about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/administration/declarative-containers.section.md4
-rw-r--r--nixos/doc/manual/administration/imperative-containers.section.md4
-rw-r--r--nixos/doc/manual/configuration/adding-custom-packages.section.md35
-rw-r--r--nixos/doc/manual/configuration/config-file.section.md2
-rw-r--r--nixos/doc/manual/configuration/config-syntax.chapter.md1
-rw-r--r--nixos/doc/manual/configuration/gpu-accel.chapter.md38
-rw-r--r--nixos/doc/manual/configuration/kubernetes.chapter.md8
-rw-r--r--nixos/doc/manual/configuration/linux-kernel.chapter.md33
-rw-r--r--nixos/doc/manual/configuration/profiles/hardened.section.md2
-rw-r--r--nixos/doc/manual/configuration/summary.section.md46
-rw-r--r--nixos/doc/manual/configuration/user-mgmt.chapter.md3
-rw-r--r--nixos/doc/manual/configuration/wireless.section.md2
-rw-r--r--nixos/doc/manual/configuration/x-windows.chapter.md1
-rw-r--r--nixos/doc/manual/configuration/xfce.chapter.md11
-rw-r--r--nixos/doc/manual/contributing-to-this-manual.chapter.md24
-rw-r--r--nixos/doc/manual/default.nix49
-rw-r--r--nixos/doc/manual/development/activation-script.section.md6
-rw-r--r--nixos/doc/manual/development/bootspec.chapter.md36
-rw-r--r--nixos/doc/manual/development/development.xml1
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md27
-rw-r--r--nixos/doc/manual/development/option-def.section.md26
-rw-r--r--nixos/doc/manual/development/option-types.section.md98
-rw-r--r--nixos/doc/manual/development/running-nixos-tests-interactively.section.md14
-rw-r--r--nixos/doc/manual/development/running-nixos-tests.section.md17
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.section.md152
-rw-r--r--nixos/doc/manual/from_md/administration/declarative-containers.section.xml6
-rw-r--r--nixos/doc/manual/from_md/administration/imperative-containers.section.xml5
-rw-r--r--nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml116
-rw-r--r--nixos/doc/manual/from_md/configuration/config-file.section.xml2
-rw-r--r--nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml1
-rw-r--r--nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml46
-rw-r--r--nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml11
-rw-r--r--nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml47
-rw-r--r--nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml2
-rw-r--r--nixos/doc/manual/from_md/configuration/summary.section.xml332
-rw-r--r--nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml2
-rw-r--r--nixos/doc/manual/from_md/configuration/wireless.section.xml2
-rw-r--r--nixos/doc/manual/from_md/configuration/x-windows.chapter.xml1
-rw-r--r--nixos/doc/manual/from_md/configuration/xfce.chapter.xml14
-rw-r--r--nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml32
-rw-r--r--nixos/doc/manual/from_md/development/activation-script.section.xml6
-rw-r--r--nixos/doc/manual/from_md/development/bootspec.chapter.xml73
-rw-r--r--nixos/doc/manual/from_md/development/option-declarations.section.xml30
-rw-r--r--nixos/doc/manual/from_md/development/option-def.section.xml40
-rw-r--r--nixos/doc/manual/from_md/development/option-types.section.xml474
-rw-r--r--nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml43
-rw-r--r--nixos/doc/manual/from_md/development/running-nixos-tests.section.xml17
-rw-r--r--nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml296
-rw-r--r--nixos/doc/manual/from_md/installation/building-nixos.chapter.xml9
-rw-r--r--nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml6
-rw-r--r--nixos/doc/manual/from_md/installation/installing-kexec.section.xml94
-rw-r--r--nixos/doc/manual/from_md/installation/installing-usb.section.xml148
-rw-r--r--nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml2
-rw-r--r--nixos/doc/manual/from_md/installation/installing.chapter.xml1076
-rw-r--r--nixos/doc/manual/from_md/installation/obtaining.chapter.xml29
-rw-r--r--nixos/doc/manual/from_md/installation/upgrading.chapter.xml16
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-1603.section.xml2
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-1709.section.xml2
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-1903.section.xml2
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-1909.section.xml4
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2105.section.xml2
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2111.section.xml37
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2205.section.xml849
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2211.section.xml1841
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2305.section.xml380
-rw-r--r--nixos/doc/manual/installation/building-nixos.chapter.md7
-rw-r--r--nixos/doc/manual/installation/installing-from-other-distro.section.md6
-rw-r--r--nixos/doc/manual/installation/installing-kexec.section.md64
-rw-r--r--nixos/doc/manual/installation/installing-usb.section.md93
-rw-r--r--nixos/doc/manual/installation/installing-virtualbox-guest.section.md2
-rw-r--r--nixos/doc/manual/installation/installing.chapter.md213
-rw-r--r--nixos/doc/manual/installation/obtaining.chapter.md19
-rw-r--r--nixos/doc/manual/installation/upgrading.chapter.md16
-rw-r--r--nixos/doc/manual/man-nixos-rebuild.xml18
-rwxr-xr-xnixos/doc/manual/md-to-db.sh1
-rw-r--r--nixos/doc/manual/release-notes/release-notes.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-1603.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-1709.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-1903.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.section.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-2105.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2111.section.md10
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md298
-rw-r--r--nixos/doc/manual/release-notes/rl-2211.section.md536
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md102
-rw-r--r--nixos/lib/build-vms.nix113
-rw-r--r--nixos/lib/default.nix8
-rw-r--r--nixos/lib/eval-config.nix13
-rw-r--r--nixos/lib/make-ext4-fs.nix9
-rw-r--r--nixos/lib/make-multi-disk-zfs-image.nix9
-rw-r--r--nixos/lib/make-options-doc/default.nix92
-rw-r--r--nixos/lib/make-options-doc/generateAsciiDoc.py37
-rw-r--r--nixos/lib/make-options-doc/generateCommonMark.py27
-rw-r--r--nixos/lib/make-options-doc/generateDoc.py108
-rw-r--r--nixos/lib/make-options-doc/mergeJSON.py243
-rw-r--r--nixos/lib/make-options-doc/options-to-docbook.xsl130
-rw-r--r--nixos/lib/make-single-disk-zfs-image.nix11
-rw-r--r--nixos/lib/make-system-tarball.nix2
-rw-r--r--nixos/lib/qemu-common.nix62
-rw-r--r--nixos/lib/systemd-lib.nix49
-rw-r--r--nixos/lib/systemd-types.nix34
-rw-r--r--nixos/lib/systemd-unit-options.nix248
-rw-r--r--nixos/lib/test-driver/default.nix16
-rw-r--r--nixos/lib/test-driver/setup.py1
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py17
-rw-r--r--nixos/lib/test-driver/test_driver/py.typed0
-rw-r--r--nixos/lib/test-driver/test_driver/vlan.py8
-rw-r--r--nixos/lib/test-script-prepend.py42
-rw-r--r--nixos/lib/testing-python.nix241
-rw-r--r--nixos/lib/testing/call-test.nix12
-rw-r--r--nixos/lib/testing/default.nix24
-rw-r--r--nixos/lib/testing/driver.nix188
-rw-r--r--nixos/lib/testing/interactive.nix45
-rw-r--r--nixos/lib/testing/legacy.nix25
-rw-r--r--nixos/lib/testing/meta.nix42
-rw-r--r--nixos/lib/testing/name.nix14
-rw-r--r--nixos/lib/testing/network.nix117
-rw-r--r--nixos/lib/testing/nixos-test-base.nix23
-rw-r--r--nixos/lib/testing/nodes.nix112
-rw-r--r--nixos/lib/testing/pkgs.nix11
-rw-r--r--nixos/lib/testing/run.nix57
-rw-r--r--nixos/lib/testing/testScript.nix84
-rw-r--r--nixos/lib/utils.nix36
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix17
-rwxr-xr-xnixos/maintainers/scripts/ec2/create-amis.sh30
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-image-inner.nix7
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-image.nix5
-rw-r--r--nixos/maintainers/scripts/openstack/openstack-image-zfs.nix6
-rw-r--r--nixos/modules/config/appstream.nix4
-rw-r--r--nixos/modules/config/console.nix53
-rw-r--r--nixos/modules/config/debug-info.nix19
-rw-r--r--nixos/modules/config/fonts/fontconfig.nix73
-rw-r--r--nixos/modules/config/fonts/fontdir.nix8
-rw-r--r--nixos/modules/config/fonts/fonts.nix4
-rw-r--r--nixos/modules/config/fonts/ghostscript.nix2
-rw-r--r--nixos/modules/config/gnu.nix2
-rw-r--r--nixos/modules/config/gtk/gtk-icon-cache.nix6
-rw-r--r--nixos/modules/config/i18n.nix39
-rw-r--r--nixos/modules/config/iproute2.nix4
-rw-r--r--nixos/modules/config/krb5/default.nix66
-rw-r--r--nixos/modules/config/ldap.nix52
-rw-r--r--nixos/modules/config/locale.nix19
-rw-r--r--nixos/modules/config/malloc.nix19
-rw-r--r--nixos/modules/config/mysql.nix456
-rw-r--r--nixos/modules/config/networking.nix32
-rw-r--r--nixos/modules/config/no-x-libs.nix16
-rw-r--r--nixos/modules/config/nsswitch.nix24
-rw-r--r--nixos/modules/config/power-management.nix12
-rw-r--r--nixos/modules/config/pulseaudio.nix39
-rw-r--r--nixos/modules/config/qt5.nix74
-rw-r--r--nixos/modules/config/resolvconf.nix41
-rw-r--r--nixos/modules/config/shells-environment.nix32
-rw-r--r--nixos/modules/config/swap.nix36
-rw-r--r--nixos/modules/config/sysctl.nix25
-rw-r--r--nixos/modules/config/system-environment.nix21
-rw-r--r--nixos/modules/config/system-path.nix29
-rw-r--r--nixos/modules/config/terminfo.nix2
-rw-r--r--nixos/modules/config/unix-odbc-drivers.nix6
-rw-r--r--nixos/modules/config/update-users-groups.pl10
-rw-r--r--nixos/modules/config/users-groups.nix244
-rw-r--r--nixos/modules/config/vte.nix4
-rw-r--r--nixos/modules/config/xdg/autostart.nix4
-rw-r--r--nixos/modules/config/xdg/icons.nix12
-rw-r--r--nixos/modules/config/xdg/menus.nix4
-rw-r--r--nixos/modules/config/xdg/mime.nix24
-rw-r--r--nixos/modules/config/xdg/portal.nix53
-rw-r--r--nixos/modules/config/xdg/portals/lxqt.nix49
-rw-r--r--nixos/modules/config/xdg/portals/wlr.nix14
-rw-r--r--nixos/modules/config/xdg/sounds.nix4
-rw-r--r--nixos/modules/config/zram.nix49
-rw-r--r--nixos/modules/hardware/acpilight.nix2
-rw-r--r--nixos/modules/hardware/all-firmware.nix19
-rw-r--r--nixos/modules/hardware/bladeRF.nix2
-rw-r--r--nixos/modules/hardware/brillo.nix9
-rw-r--r--nixos/modules/hardware/ckb-next.nix8
-rw-r--r--nixos/modules/hardware/corectrl.nix10
-rw-r--r--nixos/modules/hardware/cpu/amd-microcode.nix2
-rw-r--r--nixos/modules/hardware/cpu/amd-sev.nix51
-rw-r--r--nixos/modules/hardware/cpu/intel-microcode.nix2
-rw-r--r--nixos/modules/hardware/cpu/intel-sgx.nix16
-rw-r--r--nixos/modules/hardware/device-tree.nix71
-rw-r--r--nixos/modules/hardware/digitalbitbox.nix4
-rw-r--r--nixos/modules/hardware/flirc.nix2
-rw-r--r--nixos/modules/hardware/gkraken.nix2
-rw-r--r--nixos/modules/hardware/gpgsmartcards.nix6
-rw-r--r--nixos/modules/hardware/hackrf.nix2
-rw-r--r--nixos/modules/hardware/i2c.nix6
-rw-r--r--nixos/modules/hardware/keyboard/teck.nix2
-rw-r--r--nixos/modules/hardware/keyboard/uhk.nix21
-rw-r--r--nixos/modules/hardware/keyboard/zsa.nix2
-rw-r--r--nixos/modules/hardware/ksm.nix6
-rw-r--r--nixos/modules/hardware/ledger.nix2
-rw-r--r--nixos/modules/hardware/logitech.nix13
-rw-r--r--nixos/modules/hardware/mcelog.nix2
-rw-r--r--nixos/modules/hardware/network/ath-user-regd.nix2
-rw-r--r--nixos/modules/hardware/network/b43.nix2
-rw-r--r--nixos/modules/hardware/network/intel-2200bg.nix2
-rw-r--r--nixos/modules/hardware/new-lg4ff.nix29
-rw-r--r--nixos/modules/hardware/nitrokey.nix2
-rw-r--r--nixos/modules/hardware/onlykey/default.nix2
-rw-r--r--nixos/modules/hardware/opengl.nix28
-rw-r--r--nixos/modules/hardware/openrazer.nix18
-rw-r--r--nixos/modules/hardware/opentabletdriver.nix8
-rw-r--r--nixos/modules/hardware/pcmcia.nix6
-rw-r--r--nixos/modules/hardware/printers.nix26
-rw-r--r--nixos/modules/hardware/raid/hpsa.nix4
-rw-r--r--nixos/modules/hardware/rtl-sdr.nix2
-rw-r--r--nixos/modules/hardware/saleae-logic.nix25
-rw-r--r--nixos/modules/hardware/sata.nix10
-rw-r--r--nixos/modules/hardware/sensor/hddtemp.nix10
-rw-r--r--nixos/modules/hardware/sensor/iio.nix2
-rw-r--r--nixos/modules/hardware/steam-hardware.nix2
-rw-r--r--nixos/modules/hardware/system-76.nix8
-rw-r--r--nixos/modules/hardware/tuxedo-keyboard.nix14
-rw-r--r--nixos/modules/hardware/ubertooth.nix4
-rw-r--r--nixos/modules/hardware/uinput.nix2
-rw-r--r--nixos/modules/hardware/usb-storage.nix20
-rw-r--r--nixos/modules/hardware/usb-wwan.nix2
-rw-r--r--nixos/modules/hardware/video/bumblebee.nix10
-rw-r--r--nixos/modules/hardware/video/capture/mwprocapture.nix2
-rw-r--r--nixos/modules/hardware/video/hidpi.nix10
-rw-r--r--nixos/modules/hardware/video/nvidia.nix107
-rw-r--r--nixos/modules/hardware/video/switcheroo-control.nix2
-rw-r--r--nixos/modules/hardware/video/uvcvideo/default.nix20
-rw-r--r--nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix4
-rw-r--r--nixos/modules/hardware/video/webcam/facetimehd.nix17
-rw-r--r--nixos/modules/hardware/wooting.nix2
-rw-r--r--nixos/modules/hardware/xone.nix2
-rw-r--r--nixos/modules/hardware/xpadneo.nix5
-rw-r--r--nixos/modules/i18n/input-method/default.nix20
-rw-r--r--nixos/modules/i18n/input-method/fcitx.nix4
-rw-r--r--nixos/modules/i18n/input-method/fcitx5.nix30
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix10
-rw-r--r--nixos/modules/i18n/input-method/kime.nix4
-rw-r--r--nixos/modules/i18n/input-method/uim.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/channel.nix17
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-base.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix31
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix60
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma5.nix49
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix23
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-minimal.nix15
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix122
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix160
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt89
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-pc.nix163
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix172
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball.nix93
-rw-r--r--nixos/modules/installer/kexec/kexec-boot.nix51
-rw-r--r--nixos/modules/installer/netboot/netboot-minimal.nix12
-rw-r--r--nixos/modules/installer/netboot/netboot.nix35
-rw-r--r--nixos/modules/installer/sd-card/sd-image.nix58
-rw-r--r--nixos/modules/installer/tools/get-version-suffix7
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/nixos-build-vms/build-vms.nix2
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl27
-rw-r--r--nixos/modules/installer/tools/tools.nix28
-rw-r--r--nixos/modules/misc/assertions.nix4
-rw-r--r--nixos/modules/misc/crashdump.nix6
-rw-r--r--nixos/modules/misc/documentation.nix140
-rw-r--r--nixos/modules/misc/documentation/test-dummy.chapter.xml0
-rw-r--r--nixos/modules/misc/documentation/test.nix49
-rw-r--r--nixos/modules/misc/ids.nix35
-rw-r--r--nixos/modules/misc/label.nix28
-rw-r--r--nixos/modules/misc/lib.nix2
-rw-r--r--nixos/modules/misc/locate.nix39
-rw-r--r--nixos/modules/misc/man-db.nix20
-rw-r--r--nixos/modules/misc/mandoc.nix14
-rw-r--r--nixos/modules/misc/meta.nix6
-rw-r--r--nixos/modules/misc/nixops-autoluks.nix2
-rw-r--r--nixos/modules/misc/nixpkgs.nix215
-rw-r--r--nixos/modules/misc/nixpkgs/test.nix67
-rw-r--r--nixos/modules/misc/passthru.nix2
-rw-r--r--nixos/modules/misc/version.nix37
-rw-r--r--nixos/modules/misc/wordlist.nix4
-rw-r--r--nixos/modules/module-list.nix463
-rw-r--r--nixos/modules/profiles/all-hardware.nix7
-rw-r--r--nixos/modules/profiles/base.nix15
-rw-r--r--nixos/modules/profiles/clone-config.nix6
-rw-r--r--nixos/modules/profiles/docker-container.nix11
-rw-r--r--nixos/modules/profiles/installation-device.nix10
-rw-r--r--nixos/modules/profiles/keys/ssh_host_ed25519_key7
-rw-r--r--nixos/modules/profiles/keys/ssh_host_ed25519_key.pub1
-rw-r--r--nixos/modules/profiles/macos-builder.nix134
-rw-r--r--nixos/modules/profiles/minimal.nix18
-rw-r--r--nixos/modules/profiles/qemu-guest.nix4
-rw-r--r--nixos/modules/programs/_1password-gui.nix21
-rw-r--r--nixos/modules/programs/_1password.nix19
-rw-r--r--nixos/modules/programs/adb.nix4
-rw-r--r--nixos/modules/programs/appgate-sdp.nix2
-rw-r--r--nixos/modules/programs/atop.nix40
-rw-r--r--nixos/modules/programs/ausweisapp.nix25
-rw-r--r--nixos/modules/programs/autojump.nix2
-rw-r--r--nixos/modules/programs/bandwhich.nix2
-rw-r--r--nixos/modules/programs/bash-my-aws.nix2
-rw-r--r--nixos/modules/programs/bash/bash-completion.nix2
-rw-r--r--nixos/modules/programs/bash/bash.nix22
-rw-r--r--nixos/modules/programs/bash/blesh.nix16
-rw-r--r--nixos/modules/programs/bash/ls-colors.nix2
-rw-r--r--nixos/modules/programs/bash/undistract-me.nix6
-rw-r--r--nixos/modules/programs/bcc.nix2
-rw-r--r--nixos/modules/programs/browserpass.nix2
-rw-r--r--nixos/modules/programs/calls.nix4
-rw-r--r--nixos/modules/programs/captive-browser.nix20
-rw-r--r--nixos/modules/programs/ccache.nix6
-rw-r--r--nixos/modules/programs/cdemu.nix16
-rw-r--r--nixos/modules/programs/cfs-zen-tweaks.nix28
-rw-r--r--nixos/modules/programs/chromium.nix20
-rw-r--r--nixos/modules/programs/clickshare.nix21
-rw-r--r--nixos/modules/programs/cnping.nix2
-rw-r--r--nixos/modules/programs/command-not-found/command-not-found.nix4
-rw-r--r--nixos/modules/programs/criu.nix4
-rw-r--r--nixos/modules/programs/dconf.nix6
-rw-r--r--nixos/modules/programs/digitalbitbox/default.nix4
-rw-r--r--nixos/modules/programs/dmrconfig.nix6
-rw-r--r--nixos/modules/programs/droidcam.nix2
-rw-r--r--nixos/modules/programs/evince.nix4
-rw-r--r--nixos/modules/programs/extra-container.nix4
-rw-r--r--nixos/modules/programs/feedbackd.nix6
-rw-r--r--nixos/modules/programs/file-roller.nix4
-rw-r--r--nixos/modules/programs/firefox.nix264
-rw-r--r--nixos/modules/programs/firejail.nix35
-rw-r--r--nixos/modules/programs/fish.nix32
-rw-r--r--nixos/modules/programs/flashrom.nix7
-rw-r--r--nixos/modules/programs/flexoptix-app.nix4
-rw-r--r--nixos/modules/programs/freetds.nix2
-rw-r--r--nixos/modules/programs/fuse.nix4
-rw-r--r--nixos/modules/programs/fzf.nix27
-rw-r--r--nixos/modules/programs/gamemode.nix6
-rw-r--r--nixos/modules/programs/geary.nix2
-rw-r--r--nixos/modules/programs/git.nix48
-rw-r--r--nixos/modules/programs/gnome-disks.nix2
-rw-r--r--nixos/modules/programs/gnome-documents.nix2
-rw-r--r--nixos/modules/programs/gnome-terminal.nix2
-rw-r--r--nixos/modules/programs/gnupg.nix30
-rw-r--r--nixos/modules/programs/gpaste.nix2
-rw-r--r--nixos/modules/programs/gphoto2.nix4
-rw-r--r--nixos/modules/programs/haguichi.nix15
-rw-r--r--nixos/modules/programs/hamster.nix2
-rw-r--r--nixos/modules/programs/htop.nix8
-rw-r--r--nixos/modules/programs/i3lock.nix58
-rw-r--r--nixos/modules/programs/iftop.nix2
-rw-r--r--nixos/modules/programs/iotop.nix2
-rw-r--r--nixos/modules/programs/java.nix27
-rw-r--r--nixos/modules/programs/k3b.nix52
-rw-r--r--nixos/modules/programs/k40-whisperer.nix6
-rw-r--r--nixos/modules/programs/kbdlight.nix2
-rw-r--r--nixos/modules/programs/kclock.nix2
-rw-r--r--nixos/modules/programs/kdeconnect.nix14
-rw-r--r--nixos/modules/programs/less.nix25
-rw-r--r--nixos/modules/programs/liboping.nix2
-rw-r--r--nixos/modules/programs/light.nix2
-rw-r--r--nixos/modules/programs/mdevctl.nix18
-rw-r--r--nixos/modules/programs/mepo.nix46
-rw-r--r--nixos/modules/programs/mininet.nix4
-rw-r--r--nixos/modules/programs/mosh.nix4
-rw-r--r--nixos/modules/programs/msmtp.nix10
-rw-r--r--nixos/modules/programs/mtr.nix4
-rw-r--r--nixos/modules/programs/nano.nix6
-rw-r--r--nixos/modules/programs/nbd.nix2
-rw-r--r--nixos/modules/programs/neovim.nix50
-rw-r--r--nixos/modules/programs/nethoscope.nix2
-rw-r--r--nixos/modules/programs/nix-ld.nix64
-rw-r--r--nixos/modules/programs/nm-applet.nix4
-rw-r--r--nixos/modules/programs/nncp.nix24
-rw-r--r--nixos/modules/programs/noisetorch.nix8
-rw-r--r--nixos/modules/programs/npm.nix8
-rw-r--r--nixos/modules/programs/openvpn3.nix33
-rw-r--r--nixos/modules/programs/pantheon-tweaks.nix2
-rw-r--r--nixos/modules/programs/partition-manager.nix2
-rw-r--r--nixos/modules/programs/plotinus.nix2
-rw-r--r--nixos/modules/programs/proxychains.nix40
-rw-r--r--nixos/modules/programs/qt5ct.nix30
-rw-r--r--nixos/modules/programs/rog-control-center.nix29
-rw-r--r--nixos/modules/programs/rust-motd.nix92
-rw-r--r--nixos/modules/programs/screen.nix2
-rw-r--r--nixos/modules/programs/seahorse.nix2
-rw-r--r--nixos/modules/programs/sedutil.nix2
-rw-r--r--nixos/modules/programs/shadow.nix2
-rw-r--r--nixos/modules/programs/singularity.nix2
-rw-r--r--nixos/modules/programs/skim.nix34
-rw-r--r--nixos/modules/programs/slock.nix2
-rw-r--r--nixos/modules/programs/spacefm.nix10
-rw-r--r--nixos/modules/programs/ssh.nix79
-rw-r--r--nixos/modules/programs/starship.nix6
-rw-r--r--nixos/modules/programs/steam.nix39
-rw-r--r--nixos/modules/programs/streamdeck-ui.nix34
-rw-r--r--nixos/modules/programs/sway.nix25
-rw-r--r--nixos/modules/programs/sysdig.nix2
-rw-r--r--nixos/modules/programs/system-config-printer.nix2
-rw-r--r--nixos/modules/programs/systemtap.nix4
-rw-r--r--nixos/modules/programs/thefuck.nix17
-rw-r--r--nixos/modules/programs/thunar.nix45
-rw-r--r--nixos/modules/programs/tmux.nix51
-rw-r--r--nixos/modules/programs/traceroute.nix2
-rw-r--r--nixos/modules/programs/tsm-client.nix86
-rw-r--r--nixos/modules/programs/turbovnc.nix6
-rw-r--r--nixos/modules/programs/udevil.nix2
-rw-r--r--nixos/modules/programs/usbtop.nix2
-rw-r--r--nixos/modules/programs/vim.nix6
-rw-r--r--nixos/modules/programs/wavemon.nix2
-rw-r--r--nixos/modules/programs/waybar.nix2
-rw-r--r--nixos/modules/programs/weylus.nix10
-rw-r--r--nixos/modules/programs/wireshark.nix4
-rw-r--r--nixos/modules/programs/wshowkeys.nix4
-rw-r--r--nixos/modules/programs/xfconf.nix27
-rw-r--r--nixos/modules/programs/xfs_quota.nix14
-rw-r--r--nixos/modules/programs/xonsh.nix10
-rw-r--r--nixos/modules/programs/xss-lock.nix8
-rw-r--r--nixos/modules/programs/xwayland.nix6
-rw-r--r--nixos/modules/programs/yabar.nix20
-rw-r--r--nixos/modules/programs/zmap.nix2
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.nix14
-rw-r--r--nixos/modules/programs/zsh/zsh-autoenv.nix4
-rw-r--r--nixos/modules/programs/zsh/zsh-autosuggestions.nix10
-rw-r--r--nixos/modules/programs/zsh/zsh-syntax-highlighting.nix8
-rw-r--r--nixos/modules/programs/zsh/zsh.nix46
-rw-r--r--nixos/modules/rename.nix8
-rw-r--r--nixos/modules/security/acme/default.nix108
-rw-r--r--nixos/modules/security/acme/doc.xml15
-rw-r--r--nixos/modules/security/apparmor.nix36
-rw-r--r--nixos/modules/security/audit.nix10
-rw-r--r--nixos/modules/security/auditd.nix2
-rw-r--r--nixos/modules/security/ca.nix12
-rw-r--r--nixos/modules/security/chromium-suid-sandbox.nix2
-rw-r--r--nixos/modules/security/dhparams.nix54
-rw-r--r--nixos/modules/security/doas.nix72
-rw-r--r--nixos/modules/security/duosec.nix52
-rw-r--r--nixos/modules/security/google_oslogin.nix2
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix4
-rw-r--r--nixos/modules/security/misc.nix49
-rw-r--r--nixos/modules/security/oath.nix8
-rw-r--r--nixos/modules/security/pam.nix562
-rw-r--r--nixos/modules/security/pam_mount.nix33
-rw-r--r--nixos/modules/security/pam_usb.nix5
-rw-r--r--nixos/modules/security/please.nix122
-rw-r--r--nixos/modules/security/polkit.nix18
-rw-r--r--nixos/modules/security/rtkit.nix2
-rw-r--r--nixos/modules/security/sudo.nix54
-rw-r--r--nixos/modules/security/systemd-confinement.nix70
-rw-r--r--nixos/modules/security/tpm2.nix52
-rw-r--r--nixos/modules/security/wrappers/default.nix74
-rw-r--r--nixos/modules/security/wrappers/wrapper.c38
-rw-r--r--nixos/modules/services/admin/meshcentral.nix16
-rw-r--r--nixos/modules/services/admin/oxidized.nix12
-rw-r--r--nixos/modules/services/admin/pgadmin.nix82
-rw-r--r--nixos/modules/services/admin/salt/master.nix4
-rw-r--r--nixos/modules/services/admin/salt/minion.nix6
-rw-r--r--nixos/modules/services/amqp/activemq/default.nix16
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix40
-rw-r--r--nixos/modules/services/audio/alsa.nix12
-rw-r--r--nixos/modules/services/audio/botamusique.nix21
-rw-r--r--nixos/modules/services/audio/hqplayerd.nix14
-rw-r--r--nixos/modules/services/audio/icecast.nix22
-rw-r--r--nixos/modules/services/audio/jack.nix24
-rw-r--r--nixos/modules/services/audio/jmusicbot.nix6
-rw-r--r--nixos/modules/services/audio/liquidsoap.nix14
-rw-r--r--nixos/modules/services/audio/mopidy.nix12
-rw-r--r--nixos/modules/services/audio/mpd.nix41
-rw-r--r--nixos/modules/services/audio/mpdscribble.nix28
-rw-r--r--nixos/modules/services/audio/navidrome.nix11
-rw-r--r--nixos/modules/services/audio/networkaudiod.nix2
-rw-r--r--nixos/modules/services/audio/roon-bridge.nix8
-rw-r--r--nixos/modules/services/audio/roon-server.nix13
-rw-r--r--nixos/modules/services/audio/slimserver.nix6
-rw-r--r--nixos/modules/services/audio/snapserver.nix61
-rw-r--r--nixos/modules/services/audio/spotifyd.nix10
-rw-r--r--nixos/modules/services/audio/squeezelite.nix6
-rw-r--r--nixos/modules/services/audio/ympd.nix10
-rw-r--r--nixos/modules/services/backup/automysqlbackup.nix10
-rw-r--r--nixos/modules/services/backup/bacula.nix88
-rw-r--r--nixos/modules/services/backup/borgbackup.nix243
-rw-r--r--nixos/modules/services/backup/borgbackup.xml2
-rw-r--r--nixos/modules/services/backup/borgmatic.nix13
-rw-r--r--nixos/modules/services/backup/btrbk.nix184
-rw-r--r--nixos/modules/services/backup/duplicati.nix22
-rw-r--r--nixos/modules/services/backup/duplicity.nix56
-rw-r--r--nixos/modules/services/backup/mysql-backup.nix12
-rw-r--r--nixos/modules/services/backup/postgresql-backup.nix49
-rw-r--r--nixos/modules/services/backup/postgresql-wal-receiver.nix50
-rw-r--r--nixos/modules/services/backup/restic-rest-server.nix16
-rw-r--r--nixos/modules/services/backup/restic.nix249
-rw-r--r--nixos/modules/services/backup/rsnapshot.nix8
-rw-r--r--nixos/modules/services/backup/sanoid.nix43
-rw-r--r--nixos/modules/services/backup/syncoid.nix113
-rw-r--r--nixos/modules/services/backup/tarsnap.nix81
-rw-r--r--nixos/modules/services/backup/tsm.nix30
-rw-r--r--nixos/modules/services/backup/zfs-replication.nix16
-rw-r--r--nixos/modules/services/backup/znapzend.nix150
-rw-r--r--nixos/modules/services/backup/zrepl.nix18
-rw-r--r--nixos/modules/services/blockchain/ethereum/erigon.nix120
-rw-r--r--nixos/modules/services/blockchain/ethereum/geth.nix80
-rw-r--r--nixos/modules/services/blockchain/ethereum/lighthouse.nix315
-rw-r--r--nixos/modules/services/cluster/corosync/default.nix16
-rw-r--r--nixos/modules/services/cluster/hadoop/conf.nix3
-rw-r--r--nixos/modules/services/cluster/hadoop/default.nix46
-rw-r--r--nixos/modules/services/cluster/hadoop/hbase.nix196
-rw-r--r--nixos/modules/services/cluster/hadoop/hdfs.nix26
-rw-r--r--nixos/modules/services/cluster/hadoop/yarn.nix36
-rw-r--r--nixos/modules/services/cluster/k3s/default.nix90
-rw-r--r--nixos/modules/services/cluster/kubernetes/addon-manager.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dns.nix24
-rw-r--r--nixos/modules/services/cluster/kubernetes/apiserver.nix107
-rw-r--r--nixos/modules/services/cluster/kubernetes/controller-manager.nix37
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix32
-rw-r--r--nixos/modules/services/cluster/kubernetes/flannel.nix3
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix87
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix26
-rw-r--r--nixos/modules/services/cluster/kubernetes/proxy.nix14
-rw-r--r--nixos/modules/services/cluster/kubernetes/scheduler.nix18
-rw-r--r--nixos/modules/services/cluster/pacemaker/default.nix4
-rw-r--r--nixos/modules/services/cluster/patroni/default.nix268
-rw-r--r--nixos/modules/services/cluster/spark/default.nix24
-rw-r--r--nixos/modules/services/computing/boinc/client.nix55
-rw-r--r--nixos/modules/services/computing/foldingathome/client.nix14
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix89
-rw-r--r--nixos/modules/services/computing/torque/mom.nix4
-rw-r--r--nixos/modules/services/computing/torque/server.nix2
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix62
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/worker.nix32
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agents.nix30
-rw-r--r--nixos/modules/services/continuous-integration/github-runner.nix325
-rw-r--r--nixos/modules/services/continuous-integration/github-runner/options.nix173
-rw-r--r--nixos/modules/services/continuous-integration/github-runner/service.nix257
-rw-r--r--nixos/modules/services/continuous-integration/github-runners.nix56
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix275
-rw-r--r--nixos/modules/services/continuous-integration/gocd-agent/default.nix28
-rw-r--r--nixos/modules/services/continuous-integration/gocd-server/default.nix38
-rw-r--r--nixos/modules/services/continuous-integration/hail.nix14
-rw-r--r--nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix61
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix65
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/default.nix48
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/job-builder.nix50
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/slave.nix10
-rw-r--r--nixos/modules/services/databases/aerospike.nix10
-rw-r--r--nixos/modules/services/databases/cassandra.nix168
-rw-r--r--nixos/modules/services/databases/clickhouse.nix6
-rw-r--r--nixos/modules/services/databases/cockroachdb.nix44
-rw-r--r--nixos/modules/services/databases/couchdb.nix44
-rw-r--r--nixos/modules/services/databases/dgraph.nix148
-rw-r--r--nixos/modules/services/databases/dragonflydb.nix152
-rw-r--r--nixos/modules/services/databases/firebird.nix16
-rw-r--r--nixos/modules/services/databases/foundationdb.nix80
-rw-r--r--nixos/modules/services/databases/hbase-standalone.nix (renamed from nixos/modules/services/databases/hbase.nix)39
-rw-r--r--nixos/modules/services/databases/influxdb.nix18
-rw-r--r--nixos/modules/services/databases/influxdb2.nix7
-rw-r--r--nixos/modules/services/databases/memcached.nix16
-rw-r--r--nixos/modules/services/databases/monetdb.nix14
-rw-r--r--nixos/modules/services/databases/mongodb.nix26
-rw-r--r--nixos/modules/services/databases/mysql.nix94
-rw-r--r--nixos/modules/services/databases/neo4j.nix272
-rw-r--r--nixos/modules/services/databases/openldap.nix217
-rw-r--r--nixos/modules/services/databases/opentsdb.nix20
-rw-r--r--nixos/modules/services/databases/pgmanage.nix38
-rw-r--r--nixos/modules/services/databases/postgresql.nix250
-rw-r--r--nixos/modules/services/databases/postgresql.xml33
-rw-r--r--nixos/modules/services/databases/redis.nix115
-rw-r--r--nixos/modules/services/databases/rethinkdb.nix10
-rw-r--r--nixos/modules/services/databases/riak.nix162
-rw-r--r--nixos/modules/services/databases/surrealdb.nix113
-rw-r--r--nixos/modules/services/databases/victoriametrics.nix16
-rw-r--r--nixos/modules/services/desktops/accountsservice.nix2
-rw-r--r--nixos/modules/services/desktops/bamf.nix2
-rw-r--r--nixos/modules/services/desktops/blueman.nix2
-rw-r--r--nixos/modules/services/desktops/cpupower-gui.nix2
-rw-r--r--nixos/modules/services/desktops/dleyna-renderer.nix2
-rw-r--r--nixos/modules/services/desktops/dleyna-server.nix2
-rw-r--r--nixos/modules/services/desktops/espanso.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.nix2
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix34
-rw-r--r--nixos/modules/services/desktops/gnome/at-spi2-core.nix9
-rw-r--r--nixos/modules/services/desktops/gnome/chrome-gnome-shell.nix41
-rw-r--r--nixos/modules/services/desktops/gnome/evolution-data-server.nix8
-rw-r--r--nixos/modules/services/desktops/gnome/glib-networking.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-browser-connector.nix47
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-initial-setup.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-keyring.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-online-accounts.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-online-miners.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-user-share.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/rygel.nix4
-rw-r--r--nixos/modules/services/desktops/gnome/sushi.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/tracker-miners.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/tracker.nix4
-rw-r--r--nixos/modules/services/desktops/gsignond.nix4
-rw-r--r--nixos/modules/services/desktops/gvfs.nix6
-rw-r--r--nixos/modules/services/desktops/malcontent.nix2
-rw-r--r--nixos/modules/services/desktops/neard.nix2
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json28
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json2
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json38
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json3
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json1
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire-media-session.nix12
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix37
-rw-r--r--nixos/modules/services/desktops/pipewire/wireplumber.nix29
-rw-r--r--nixos/modules/services/desktops/profile-sync-daemon.nix4
-rw-r--r--nixos/modules/services/desktops/system-config-printer.nix5
-rw-r--r--nixos/modules/services/desktops/telepathy.nix2
-rw-r--r--nixos/modules/services/desktops/tumbler.nix2
-rw-r--r--nixos/modules/services/desktops/zeitgeist.nix2
-rw-r--r--nixos/modules/services/development/blackfire.nix8
-rw-r--r--nixos/modules/services/development/bloop.nix4
-rw-r--r--nixos/modules/services/development/distccd.nix24
-rw-r--r--nixos/modules/services/development/hoogle.nix14
-rw-r--r--nixos/modules/services/development/jupyter/default.nix40
-rw-r--r--nixos/modules/services/development/jupyter/kernel-options.nix32
-rw-r--r--nixos/modules/services/development/jupyterhub/default.nix22
-rw-r--r--nixos/modules/services/development/lorri.nix4
-rw-r--r--nixos/modules/services/development/rstudio-server/default.nix12
-rw-r--r--nixos/modules/services/development/zammad.nix48
-rw-r--r--nixos/modules/services/display-managers/greetd.nix16
-rw-r--r--nixos/modules/services/editors/emacs.nix16
-rw-r--r--nixos/modules/services/editors/haste.nix8
-rw-r--r--nixos/modules/services/editors/infinoted.nix26
-rw-r--r--nixos/modules/services/finance/odoo.nix12
-rw-r--r--nixos/modules/services/games/asf.nix125
-rw-r--r--nixos/modules/services/games/crossfire-server.nix20
-rw-r--r--nixos/modules/services/games/deliantra-server.nix14
-rw-r--r--nixos/modules/services/games/factorio.nix72
-rw-r--r--nixos/modules/services/games/freeciv.nix28
-rw-r--r--nixos/modules/services/games/minecraft-server.nix78
-rw-r--r--nixos/modules/services/games/minetest-server.nix14
-rw-r--r--nixos/modules/services/games/openarena.nix6
-rw-r--r--nixos/modules/services/games/quake3-server.nix12
-rw-r--r--nixos/modules/services/games/teeworlds.nix22
-rw-r--r--nixos/modules/services/games/terraria.nix38
-rw-r--r--nixos/modules/services/hardware/acpid.nix22
-rw-r--r--nixos/modules/services/hardware/actkbd.nix26
-rw-r--r--nixos/modules/services/hardware/argonone.nix58
-rw-r--r--nixos/modules/services/hardware/asusd.nix114
-rw-r--r--nixos/modules/services/hardware/auto-cpufreq.nix2
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix18
-rw-r--r--nixos/modules/services/hardware/bolt.nix2
-rw-r--r--nixos/modules/services/hardware/brltty.nix2
-rw-r--r--nixos/modules/services/hardware/ddccontrol.nix2
-rw-r--r--nixos/modules/services/hardware/fancontrol.nix4
-rw-r--r--nixos/modules/services/hardware/freefall.nix6
-rw-r--r--nixos/modules/services/hardware/fwupd.nix123
-rw-r--r--nixos/modules/services/hardware/illum.nix3
-rw-r--r--nixos/modules/services/hardware/interception-tools.nix6
-rw-r--r--nixos/modules/services/hardware/irqbalance.nix2
-rw-r--r--nixos/modules/services/hardware/joycond.nix6
-rw-r--r--nixos/modules/services/hardware/kanata.nix215
-rw-r--r--nixos/modules/services/hardware/lcd.nix43
-rw-r--r--nixos/modules/services/hardware/lirc.nix8
-rw-r--r--nixos/modules/services/hardware/nvidia-optimus.nix2
-rw-r--r--nixos/modules/services/hardware/openrgb.nix52
-rw-r--r--nixos/modules/services/hardware/pcscd.nix19
-rw-r--r--nixos/modules/services/hardware/pommed.nix12
-rw-r--r--nixos/modules/services/hardware/power-profiles-daemon.nix2
-rw-r--r--nixos/modules/services/hardware/rasdaemon.nix14
-rw-r--r--nixos/modules/services/hardware/ratbagd.nix2
-rw-r--r--nixos/modules/services/hardware/sane.nix62
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan4.nix14
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix3
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan5.nix12
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/dsseries.nix4
-rw-r--r--nixos/modules/services/hardware/spacenavd.nix2
-rw-r--r--nixos/modules/services/hardware/supergfxd.nix38
-rw-r--r--nixos/modules/services/hardware/tcsd.nix18
-rw-r--r--nixos/modules/services/hardware/thermald.nix8
-rw-r--r--nixos/modules/services/hardware/thinkfan.nix86
-rw-r--r--nixos/modules/services/hardware/throttled.nix4
-rw-r--r--nixos/modules/services/hardware/tlp.nix6
-rw-r--r--nixos/modules/services/hardware/trezord.nix6
-rw-r--r--nixos/modules/services/hardware/triggerhappy.nix22
-rw-r--r--nixos/modules/services/hardware/udev.nix122
-rw-r--r--nixos/modules/services/hardware/udisks2.nix22
-rw-r--r--nixos/modules/services/hardware/undervolt.nix32
-rw-r--r--nixos/modules/services/hardware/upower.nix90
-rw-r--r--nixos/modules/services/hardware/usbmuxd.nix18
-rw-r--r--nixos/modules/services/hardware/usbrelayd.nix17
-rw-r--r--nixos/modules/services/hardware/vdr.nix10
-rw-r--r--nixos/modules/services/hardware/xow.nix20
-rw-r--r--nixos/modules/services/home-automation/evcc.nix96
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix146
-rw-r--r--nixos/modules/services/home-automation/zigbee2mqtt.nix17
-rw-r--r--nixos/modules/services/logging/SystemdJournal2Gelf.nix10
-rw-r--r--nixos/modules/services/logging/awstats.nix38
-rw-r--r--nixos/modules/services/logging/filebeat.nix71
-rw-r--r--nixos/modules/services/logging/fluentd.nix12
-rw-r--r--nixos/modules/services/logging/graylog.nix26
-rw-r--r--nixos/modules/services/logging/heartbeat.nix22
-rw-r--r--nixos/modules/services/logging/journalbeat.nix14
-rw-r--r--nixos/modules/services/logging/journaldriver.nix12
-rw-r--r--nixos/modules/services/logging/journalwatch.nix40
-rw-r--r--nixos/modules/services/logging/logcheck.nix38
-rw-r--r--nixos/modules/services/logging/logrotate.nix241
-rw-r--r--nixos/modules/services/logging/logstash.nix28
-rw-r--r--nixos/modules/services/logging/promtail.nix6
-rw-r--r--nixos/modules/services/logging/rsyslogd.nix18
-rw-r--r--nixos/modules/services/logging/syslog-ng.nix19
-rw-r--r--nixos/modules/services/logging/syslogd.nix22
-rw-r--r--nixos/modules/services/logging/vector.nix12
-rw-r--r--nixos/modules/services/mail/clamsmtp.nix28
-rw-r--r--nixos/modules/services/mail/davmail.nix10
-rw-r--r--nixos/modules/services/mail/dkimproxy-out.nix12
-rw-r--r--nixos/modules/services/mail/dovecot.nix70
-rw-r--r--nixos/modules/services/mail/dspam.nix14
-rw-r--r--nixos/modules/services/mail/exim.nix14
-rw-r--r--nixos/modules/services/mail/listmonk.nix222
-rw-r--r--nixos/modules/services/mail/maddy.nix31
-rw-r--r--nixos/modules/services/mail/mail.nix2
-rw-r--r--nixos/modules/services/mail/mailcatcher.nix12
-rw-r--r--nixos/modules/services/mail/mailhog.nix12
-rw-r--r--nixos/modules/services/mail/mailman.nix263
-rw-r--r--nixos/modules/services/mail/mlmmj.nix15
-rw-r--r--nixos/modules/services/mail/nullmailer.nix46
-rw-r--r--nixos/modules/services/mail/offlineimap.nix14
-rw-r--r--nixos/modules/services/mail/opendkim.nix18
-rw-r--r--nixos/modules/services/mail/opensmtpd.nix12
-rw-r--r--nixos/modules/services/mail/pfix-srsd.nix8
-rw-r--r--nixos/modules/services/mail/postfix.nix196
-rw-r--r--nixos/modules/services/mail/postfixadmin.nix30
-rw-r--r--nixos/modules/services/mail/postgrey.nix38
-rw-r--r--nixos/modules/services/mail/postsrsd.nix20
-rw-r--r--nixos/modules/services/mail/public-inbox.nix577
-rw-r--r--nixos/modules/services/mail/roundcube.nix38
-rw-r--r--nixos/modules/services/mail/rspamd.nix60
-rw-r--r--nixos/modules/services/mail/rss2email.nix31
-rw-r--r--nixos/modules/services/mail/schleuder.nix162
-rw-r--r--nixos/modules/services/mail/spamassassin.nix29
-rw-r--r--nixos/modules/services/mail/sympa.nix84
-rw-r--r--nixos/modules/services/matrix/appservice-discord.nix (renamed from nixos/modules/services/misc/matrix-appservice-discord.nix)35
-rw-r--r--nixos/modules/services/matrix/appservice-irc.nix (renamed from nixos/modules/services/misc/matrix-appservice-irc.nix)37
-rw-r--r--nixos/modules/services/matrix/conduit.nix (renamed from nixos/modules/services/misc/matrix-conduit.nix)33
-rw-r--r--nixos/modules/services/matrix/dendrite.nix (renamed from nixos/modules/services/misc/dendrite.nix)117
-rw-r--r--nixos/modules/services/matrix/mautrix-facebook.nix (renamed from nixos/modules/services/misc/mautrix-facebook.nix)34
-rw-r--r--nixos/modules/services/matrix/mautrix-telegram.nix (renamed from nixos/modules/services/misc/mautrix-telegram.nix)75
-rw-r--r--nixos/modules/services/matrix/mjolnir.nix44
-rw-r--r--nixos/modules/services/matrix/pantalaimon-options.nix20
-rw-r--r--nixos/modules/services/matrix/pantalaimon.nix4
-rw-r--r--nixos/modules/services/matrix/synapse-log_config.yaml (renamed from nixos/modules/services/matrix/matrix-synapse-log_config.yaml)0
-rw-r--r--nixos/modules/services/matrix/synapse.nix (renamed from nixos/modules/services/matrix/matrix-synapse.nix)139
-rw-r--r--nixos/modules/services/matrix/synapse.xml (renamed from nixos/modules/services/matrix/matrix-synapse.xml)237
-rw-r--r--nixos/modules/services/misc/airsonic.nix26
-rw-r--r--nixos/modules/services/misc/ananicy.nix14
-rw-r--r--nixos/modules/services/misc/ankisyncd.nix12
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix26
-rw-r--r--nixos/modules/services/misc/atuin.nix85
-rw-r--r--nixos/modules/services/misc/autofs.nix10
-rw-r--r--nixos/modules/services/misc/autorandr.nix72
-rw-r--r--nixos/modules/services/misc/bazarr.nix10
-rw-r--r--nixos/modules/services/misc/beanstalkd.nix10
-rw-r--r--nixos/modules/services/misc/bees.nix21
-rw-r--r--nixos/modules/services/misc/bepasty.nix26
-rw-r--r--nixos/modules/services/misc/calibre-server.nix8
-rw-r--r--nixos/modules/services/misc/canto-daemon.nix2
-rw-r--r--nixos/modules/services/misc/cfdyndns.nix8
-rw-r--r--nixos/modules/services/misc/cgminer.nix12
-rw-r--r--nixos/modules/services/misc/clipcat.nix4
-rw-r--r--nixos/modules/services/misc/clipmenu.nix4
-rwxr-xr-xnixos/modules/services/misc/confd.nix18
-rw-r--r--nixos/modules/services/misc/cpuminer-cryptonight.nix10
-rw-r--r--nixos/modules/services/misc/devmon.nix2
-rw-r--r--nixos/modules/services/misc/dictd.nix8
-rw-r--r--nixos/modules/services/misc/disnix.nix12
-rw-r--r--nixos/modules/services/misc/docker-registry.nix25
-rw-r--r--nixos/modules/services/misc/domoticz.nix8
-rw-r--r--nixos/modules/services/misc/duckling.nix4
-rw-r--r--nixos/modules/services/misc/dwm-status.nix8
-rw-r--r--nixos/modules/services/misc/dysnomia.nix22
-rw-r--r--nixos/modules/services/misc/errbot.nix16
-rw-r--r--nixos/modules/services/misc/etcd.nix42
-rw-r--r--nixos/modules/services/misc/etebase-server.nix40
-rw-r--r--nixos/modules/services/misc/etesync-dav.nix14
-rw-r--r--nixos/modules/services/misc/ethminer.nix117
-rw-r--r--nixos/modules/services/misc/exhibitor.nix87
-rw-r--r--nixos/modules/services/misc/felix.nix8
-rw-r--r--nixos/modules/services/misc/freeswitch.nix24
-rw-r--r--nixos/modules/services/misc/fstrim.nix7
-rw-r--r--nixos/modules/services/misc/gammu-smsd.nix48
-rw-r--r--nixos/modules/services/misc/geoipupdate.nix88
-rw-r--r--nixos/modules/services/misc/gitea.nix250
-rw-r--r--nixos/modules/services/misc/gitit.nix132
-rw-r--r--nixos/modules/services/misc/gitlab.nix331
-rw-r--r--nixos/modules/services/misc/gitlab.xml2
-rw-r--r--nixos/modules/services/misc/gitolite.nix51
-rw-r--r--nixos/modules/services/misc/gitweb.nix8
-rw-r--r--nixos/modules/services/misc/gogs.nix52
-rw-r--r--nixos/modules/services/misc/gollum.nix59
-rw-r--r--nixos/modules/services/misc/gpsd.nix23
-rw-r--r--nixos/modules/services/misc/greenclip.nix4
-rw-r--r--nixos/modules/services/misc/headphones.nix14
-rw-r--r--nixos/modules/services/misc/heisenbridge.nix29
-rw-r--r--nixos/modules/services/misc/ihaskell.nix6
-rw-r--r--nixos/modules/services/misc/input-remapper.nix6
-rw-r--r--nixos/modules/services/misc/irkerd.nix8
-rw-r--r--nixos/modules/services/misc/jackett.nix12
-rw-r--r--nixos/modules/services/misc/jellyfin.nix80
-rw-r--r--nixos/modules/services/misc/klipper.nix181
-rw-r--r--nixos/modules/services/misc/languagetool.nix78
-rw-r--r--nixos/modules/services/misc/leaps.nix8
-rw-r--r--nixos/modules/services/misc/libreddit.nix61
-rw-r--r--nixos/modules/services/misc/lidarr.nix12
-rw-r--r--nixos/modules/services/misc/lifecycled.nix30
-rw-r--r--nixos/modules/services/misc/logkeys.nix4
-rw-r--r--nixos/modules/services/misc/mame.nix10
-rw-r--r--nixos/modules/services/misc/mbpfan.nix32
-rw-r--r--nixos/modules/services/misc/mediatomb.nix56
-rw-r--r--nixos/modules/services/misc/metabase.nix16
-rw-r--r--nixos/modules/services/misc/moonraker.nix34
-rw-r--r--nixos/modules/services/misc/mx-puppet-discord.nix16
-rw-r--r--nixos/modules/services/misc/n8n.nix8
-rw-r--r--nixos/modules/services/misc/nitter.nix77
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix254
-rw-r--r--nixos/modules/services/misc/nix-gc.nix18
-rw-r--r--nixos/modules/services/misc/nix-optimise.nix7
-rw-r--r--nixos/modules/services/misc/nix-ssh-serve.nix8
-rw-r--r--nixos/modules/services/misc/novacomd.nix2
-rw-r--r--nixos/modules/services/misc/ntfy-sh.nix100
-rw-r--r--nixos/modules/services/misc/nzbget.nix10
-rw-r--r--nixos/modules/services/misc/nzbhydra2.nix8
-rw-r--r--nixos/modules/services/misc/octoprint.nix30
-rw-r--r--nixos/modules/services/misc/ombi.nix16
-rw-r--r--nixos/modules/services/misc/osrm.nix16
-rw-r--r--nixos/modules/services/misc/owncast.nix16
-rw-r--r--nixos/modules/services/misc/packagekit.nix8
-rw-r--r--nixos/modules/services/misc/paperless.nix87
-rw-r--r--nixos/modules/services/misc/parsoid.nix14
-rw-r--r--nixos/modules/services/misc/persistent-evdev.nix60
-rw-r--r--nixos/modules/services/misc/pinnwand.nix105
-rw-r--r--nixos/modules/services/misc/plex.nix17
-rw-r--r--nixos/modules/services/misc/plikd.nix8
-rw-r--r--nixos/modules/services/misc/podgrab.nix10
-rw-r--r--nixos/modules/services/misc/polaris.nix151
-rw-r--r--nixos/modules/services/misc/portunus.nix288
-rw-r--r--nixos/modules/services/misc/prowlarr.nix4
-rw-r--r--nixos/modules/services/misc/pykms.nix16
-rw-r--r--nixos/modules/services/misc/radarr.nix20
-rw-r--r--nixos/modules/services/misc/redmine.nix131
-rw-r--r--nixos/modules/services/misc/ripple-data-api.nix36
-rw-r--r--nixos/modules/services/misc/rippled.nix74
-rw-r--r--nixos/modules/services/misc/rmfakecloud.nix16
-rw-r--r--nixos/modules/services/misc/safeeyes.nix4
-rw-r--r--nixos/modules/services/misc/sdrplay.nix10
-rw-r--r--nixos/modules/services/misc/serviio.nix6
-rw-r--r--nixos/modules/services/misc/sickbeard.nix16
-rw-r--r--nixos/modules/services/misc/signald.nix8
-rw-r--r--nixos/modules/services/misc/siproxd.nix28
-rw-r--r--nixos/modules/services/misc/snapper.nix22
-rw-r--r--nixos/modules/services/misc/sonarr.nix21
-rw-r--r--nixos/modules/services/misc/sourcehut/builds.nix236
-rw-r--r--nixos/modules/services/misc/sourcehut/default.nix329
-rw-r--r--nixos/modules/services/misc/sourcehut/dispatch.nix127
-rw-r--r--nixos/modules/services/misc/sourcehut/git.nix217
-rw-r--r--nixos/modules/services/misc/sourcehut/hg.nix175
-rw-r--r--nixos/modules/services/misc/sourcehut/hub.nix120
-rw-r--r--nixos/modules/services/misc/sourcehut/lists.nix187
-rw-r--r--nixos/modules/services/misc/sourcehut/man.nix124
-rw-r--r--nixos/modules/services/misc/sourcehut/meta.nix213
-rw-r--r--nixos/modules/services/misc/sourcehut/paste.nix135
-rw-r--r--nixos/modules/services/misc/sourcehut/service.nix24
-rw-r--r--nixos/modules/services/misc/sourcehut/todo.nix163
-rw-r--r--nixos/modules/services/misc/spice-vdagentd.nix2
-rw-r--r--nixos/modules/services/misc/spice-webdavd.nix38
-rw-r--r--nixos/modules/services/misc/ssm-agent.nix4
-rw-r--r--nixos/modules/services/misc/sssd.nix86
-rw-r--r--nixos/modules/services/misc/subsonic.nix22
-rw-r--r--nixos/modules/services/misc/sundtek.nix2
-rw-r--r--nixos/modules/services/misc/svnserve.nix4
-rw-r--r--nixos/modules/services/misc/synergy.nix24
-rw-r--r--nixos/modules/services/misc/sysprof.nix2
-rw-r--r--nixos/modules/services/misc/tandoor-recipes.nix144
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix109
-rw-r--r--nixos/modules/services/misc/tautulli.nix24
-rw-r--r--nixos/modules/services/misc/tiddlywiki.nix8
-rw-r--r--nixos/modules/services/misc/tp-auto-kbbl.nix10
-rw-r--r--nixos/modules/services/misc/tzupdate.nix2
-rw-r--r--nixos/modules/services/misc/uhub.nix28
-rw-r--r--nixos/modules/services/misc/weechat.nix8
-rw-r--r--nixos/modules/services/misc/xmr-stak.nix10
-rw-r--r--nixos/modules/services/misc/xmrig.nix8
-rw-r--r--nixos/modules/services/misc/zoneminder.nix36
-rw-r--r--nixos/modules/services/misc/zookeeper.nix37
-rw-r--r--nixos/modules/services/monitoring/alerta.nix22
-rw-r--r--nixos/modules/services/monitoring/apcupsd.nix6
-rw-r--r--nixos/modules/services/monitoring/arbtt.nix14
-rw-r--r--nixos/modules/services/monitoring/bosun.nix28
-rw-r--r--nixos/modules/services/monitoring/cadvisor.nix42
-rw-r--r--nixos/modules/services/monitoring/collectd.nix20
-rw-r--r--nixos/modules/services/monitoring/das_watchdog.nix2
-rw-r--r--nixos/modules/services/monitoring/datadog-agent.nix38
-rw-r--r--nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix8
-rw-r--r--nixos/modules/services/monitoring/dd-agent/dd-agent.nix236
-rwxr-xr-xnixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults9
-rw-r--r--nixos/modules/services/monitoring/do-agent.nix2
-rw-r--r--nixos/modules/services/monitoring/fusion-inventory.nix6
-rw-r--r--nixos/modules/services/monitoring/grafana-agent.nix159
-rw-r--r--nixos/modules/services/monitoring/grafana-image-renderer.nix54
-rw-r--r--nixos/modules/services/monitoring/grafana-reporter.nix18
-rw-r--r--nixos/modules/services/monitoring/grafana.nix1557
-rw-r--r--nixos/modules/services/monitoring/graphite.nix212
-rw-r--r--nixos/modules/services/monitoring/hdaps.nix4
-rw-r--r--nixos/modules/services/monitoring/heapster.nix14
-rw-r--r--nixos/modules/services/monitoring/incron.nix16
-rw-r--r--nixos/modules/services/monitoring/kapacitor.nix38
-rw-r--r--nixos/modules/services/monitoring/karma.nix128
-rw-r--r--nixos/modules/services/monitoring/kthxbye.nix166
-rw-r--r--nixos/modules/services/monitoring/loki.nix14
-rw-r--r--nixos/modules/services/monitoring/longview.nix26
-rw-r--r--nixos/modules/services/monitoring/mackerel-agent.nix22
-rw-r--r--nixos/modules/services/monitoring/metricbeat.nix38
-rw-r--r--nixos/modules/services/monitoring/mimir.nix67
-rw-r--r--nixos/modules/services/monitoring/monit.nix4
-rw-r--r--nixos/modules/services/monitoring/munin.nix60
-rw-r--r--nixos/modules/services/monitoring/nagios.nix36
-rw-r--r--nixos/modules/services/monitoring/netdata.nix36
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.nix242
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix32
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix271
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix56
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.xml4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bind.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bird.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix14
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/collectd.nix16
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix24
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix22
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/fastly.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/flow.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/ipmi.nix41
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/json.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/kea.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/knot.nix11
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/lnd.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mail.nix62
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix10
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/minio.nix12
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix10
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginx.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/node.nix14
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nut.nix50
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/openldap.nix20
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/openvpn.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pihole.nix12
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postfix.nix24
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postgres.nix18
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/process.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pve.nix26
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/py-air-control.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix14
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/script.nix10
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix41
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/sql.nix24
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/statsd.nix19
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/tor.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unbound.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix34
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unifi.nix10
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix37
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/v2ray.nix29
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/varnish.nix16
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix28
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/zfs.nix44
-rw-r--r--nixos/modules/services/monitoring/prometheus/pushgateway.nix48
-rw-r--r--nixos/modules/services/monitoring/prometheus/sachet.nix88
-rw-r--r--nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix6
-rw-r--r--nixos/modules/services/monitoring/riemann-dash.nix6
-rw-r--r--nixos/modules/services/monitoring/riemann-tools.nix6
-rw-r--r--nixos/modules/services/monitoring/riemann.nix21
-rw-r--r--nixos/modules/services/monitoring/scollector.nix16
-rw-r--r--nixos/modules/services/monitoring/smartd.nix51
-rw-r--r--nixos/modules/services/monitoring/statsd.nix18
-rw-r--r--nixos/modules/services/monitoring/sysstat.nix6
-rw-r--r--nixos/modules/services/monitoring/teamviewer.nix4
-rw-r--r--nixos/modules/services/monitoring/telegraf.nix10
-rw-r--r--nixos/modules/services/monitoring/thanos.nix150
-rw-r--r--nixos/modules/services/monitoring/tremor-rs.nix129
-rw-r--r--nixos/modules/services/monitoring/tuptime.nix19
-rw-r--r--nixos/modules/services/monitoring/unpoller.nix (renamed from nixos/modules/services/monitoring/unifi-poller.nix)94
-rw-r--r--nixos/modules/services/monitoring/ups.nix28
-rw-r--r--nixos/modules/services/monitoring/uptime-kuma.nix75
-rw-r--r--nixos/modules/services/monitoring/uptime.nix10
-rw-r--r--nixos/modules/services/monitoring/vmagent.nix100
-rw-r--r--nixos/modules/services/monitoring/vnstat.nix2
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix22
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix42
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix40
-rw-r--r--nixos/modules/services/network-filesystems/cachefilesd.nix6
-rw-r--r--nixos/modules/services/network-filesystems/ceph.nix60
-rw-r--r--nixos/modules/services/network-filesystems/davfs2.nix8
-rw-r--r--nixos/modules/services/network-filesystems/diod.nix26
-rw-r--r--nixos/modules/services/network-filesystems/drbd.nix6
-rw-r--r--nixos/modules/services/network-filesystems/glusterfs.nix27
-rw-r--r--nixos/modules/services/network-filesystems/kbfs.nix10
-rw-r--r--nixos/modules/services/network-filesystems/kubo.nix (renamed from nixos/modules/services/network-filesystems/ipfs.nix)171
-rw-r--r--nixos/modules/services/network-filesystems/litestream/default.nix18
-rw-r--r--nixos/modules/services/network-filesystems/litestream/litestream.xml2
-rw-r--r--nixos/modules/services/network-filesystems/moosefs.nix38
-rw-r--r--nixos/modules/services/network-filesystems/netatalk.nix14
-rw-r--r--nixos/modules/services/network-filesystems/nfsd.nix30
-rw-r--r--nixos/modules/services/network-filesystems/openafs/client.nix50
-rw-r--r--nixos/modules/services/network-filesystems/openafs/lib.nix4
-rw-r--r--nixos/modules/services/network-filesystems/openafs/server.nix44
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/client.nix14
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/server.nix34
-rw-r--r--nixos/modules/services/network-filesystems/rsyncd.nix11
-rw-r--r--nixos/modules/services/network-filesystems/samba-wsdd.nix32
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix35
-rw-r--r--nixos/modules/services/network-filesystems/tahoe.nix56
-rw-r--r--nixos/modules/services/network-filesystems/u9fs.nix12
-rw-r--r--nixos/modules/services/network-filesystems/webdav-server-rs.nix12
-rw-r--r--nixos/modules/services/network-filesystems/webdav.nix20
-rw-r--r--nixos/modules/services/network-filesystems/xtreemfs.nix52
-rw-r--r--nixos/modules/services/network-filesystems/yandex-disk.nix14
-rw-r--r--nixos/modules/services/networking/3proxy.nix130
-rw-r--r--nixos/modules/services/networking/adguardhome.nix102
-rw-r--r--nixos/modules/services/networking/amuled.nix6
-rw-r--r--nixos/modules/services/networking/antennas.nix10
-rw-r--r--nixos/modules/services/networking/aria2.nix14
-rw-r--r--nixos/modules/services/networking/asterisk.nix84
-rw-r--r--nixos/modules/services/networking/atftpd.nix6
-rw-r--r--nixos/modules/services/networking/autossh.nix10
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix52
-rw-r--r--nixos/modules/services/networking/babeld.nix14
-rw-r--r--nixos/modules/services/networking/bee-clef.nix10
-rw-r--r--nixos/modules/services/networking/bee.nix14
-rw-r--r--nixos/modules/services/networking/biboumi.nix46
-rw-r--r--nixos/modules/services/networking/bind.nix60
-rw-r--r--nixos/modules/services/networking/bird-lg.nix269
-rw-r--r--nixos/modules/services/networking/bird.nix14
-rw-r--r--nixos/modules/services/networking/bitcoind.nix42
-rw-r--r--nixos/modules/services/networking/bitlbee.nix31
-rw-r--r--nixos/modules/services/networking/blockbook-frontend.nix60
-rw-r--r--nixos/modules/services/networking/blocky.nix6
-rw-r--r--nixos/modules/services/networking/charybdis.nix12
-rw-r--r--nixos/modules/services/networking/chisel-server.nix99
-rw-r--r--nixos/modules/services/networking/cjdns.nix36
-rw-r--r--nixos/modules/services/networking/cloudflare-dyndns.nix93
-rw-r--r--nixos/modules/services/networking/cloudflared.nix332
-rw-r--r--nixos/modules/services/networking/cntlm.nix20
-rw-r--r--nixos/modules/services/networking/connman.nix16
-rw-r--r--nixos/modules/services/networking/consul.nix36
-rw-r--r--nixos/modules/services/networking/coredns.nix9
-rw-r--r--nixos/modules/services/networking/corerad.nix10
-rw-r--r--nixos/modules/services/networking/coturn.nix67
-rw-r--r--nixos/modules/services/networking/create_ap.nix8
-rw-r--r--nixos/modules/services/networking/croc.nix12
-rw-r--r--nixos/modules/services/networking/dante.nix4
-rw-r--r--nixos/modules/services/networking/ddclient.nix51
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix16
-rw-r--r--nixos/modules/services/networking/dhcpd.nix20
-rw-r--r--nixos/modules/services/networking/dnscache.nix10
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy2.nix17
-rw-r--r--nixos/modules/services/networking/dnscrypt-wrapper.nix26
-rw-r--r--nixos/modules/services/networking/dnsdist.nix8
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix96
-rw-r--r--nixos/modules/services/networking/doh-proxy-rust.nix6
-rw-r--r--nixos/modules/services/networking/ejabberd.nix20
-rw-r--r--nixos/modules/services/networking/envoy.nix4
-rw-r--r--nixos/modules/services/networking/epmd.nix8
-rw-r--r--nixos/modules/services/networking/ergo.nix22
-rw-r--r--nixos/modules/services/networking/ergochat.nix12
-rw-r--r--nixos/modules/services/networking/eternal-terminal.nix12
-rw-r--r--nixos/modules/services/networking/expressvpn.nix29
-rw-r--r--nixos/modules/services/networking/fakeroute.nix4
-rw-r--r--nixos/modules/services/networking/ferm.nix8
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.md55
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.nix318
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.xml77
-rw-r--r--nixos/modules/services/networking/fireqos.nix4
-rw-r--r--nixos/modules/services/networking/firewall.nix74
-rw-r--r--nixos/modules/services/networking/flannel.nix51
-rw-r--r--nixos/modules/services/networking/freenet.nix4
-rw-r--r--nixos/modules/services/networking/freeradius.nix6
-rw-r--r--nixos/modules/services/networking/frr.nix23
-rw-r--r--nixos/modules/services/networking/gateone.nix10
-rw-r--r--nixos/modules/services/networking/gdomap.nix2
-rw-r--r--nixos/modules/services/networking/ghostunnel.nix44
-rw-r--r--nixos/modules/services/networking/git-daemon.nix18
-rw-r--r--nixos/modules/services/networking/globalprotect-vpn.nix31
-rw-r--r--nixos/modules/services/networking/gnunet.nix18
-rw-r--r--nixos/modules/services/networking/go-autoconfig.nix66
-rw-r--r--nixos/modules/services/networking/go-neb.nix20
-rw-r--r--nixos/modules/services/networking/go-shadowsocks2.nix4
-rw-r--r--nixos/modules/services/networking/gobgpd.nix6
-rw-r--r--nixos/modules/services/networking/gvpe.nix14
-rw-r--r--nixos/modules/services/networking/hans.nix22
-rw-r--r--nixos/modules/services/networking/haproxy.nix10
-rw-r--r--nixos/modules/services/networking/headscale.nix791
-rw-r--r--nixos/modules/services/networking/hostapd.nix44
-rw-r--r--nixos/modules/services/networking/htpdate.nix8
-rw-r--r--nixos/modules/services/networking/https-dns-proxy.nix52
-rw-r--r--nixos/modules/services/networking/hylafax/options.nix102
-rw-r--r--nixos/modules/services/networking/hylafax/systemd.nix2
-rw-r--r--nixos/modules/services/networking/i2p.nix2
-rw-r--r--nixos/modules/services/networking/i2pd.nix171
-rw-r--r--nixos/modules/services/networking/icecream/daemon.nix26
-rw-r--r--nixos/modules/services/networking/icecream/scheduler.nix16
-rw-r--r--nixos/modules/services/networking/inspircd.nix16
-rw-r--r--nixos/modules/services/networking/iodine.nix24
-rw-r--r--nixos/modules/services/networking/iperf3.nix22
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/default.nix34
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/ircd.conf2
-rw-r--r--nixos/modules/services/networking/iscsi/initiator.nix16
-rw-r--r--nixos/modules/services/networking/iscsi/root-initiator.nix16
-rw-r--r--nixos/modules/services/networking/iscsi/target.nix4
-rw-r--r--nixos/modules/services/networking/iwd.nix23
-rw-r--r--nixos/modules/services/networking/jibri/default.nix42
-rw-r--r--nixos/modules/services/networking/jicofo.nix22
-rw-r--r--nixos/modules/services/networking/jitsi-videobridge.nix36
-rw-r--r--nixos/modules/services/networking/kea.nix82
-rw-r--r--nixos/modules/services/networking/keepalived/default.nix60
-rw-r--r--nixos/modules/services/networking/keepalived/virtual-ip-options.nix10
-rw-r--r--nixos/modules/services/networking/keepalived/vrrp-instance-options.nix28
-rw-r--r--nixos/modules/services/networking/keepalived/vrrp-script-options.nix18
-rw-r--r--nixos/modules/services/networking/keybase.nix2
-rw-r--r--nixos/modules/services/networking/knot.nix14
-rw-r--r--nixos/modules/services/networking/kresd.nix18
-rw-r--r--nixos/modules/services/networking/lambdabot.nix6
-rw-r--r--nixos/modules/services/networking/libreswan.nix22
-rw-r--r--nixos/modules/services/networking/lldpd.nix4
-rw-r--r--nixos/modules/services/networking/logmein-hamachi.nix2
-rw-r--r--nixos/modules/services/networking/lokinet.nix157
-rw-r--r--nixos/modules/services/networking/lxd-image-server.nix14
-rw-r--r--nixos/modules/services/networking/magic-wormhole-mailbox-server.nix2
-rw-r--r--nixos/modules/services/networking/matterbridge.nix14
-rw-r--r--nixos/modules/services/networking/minidlna.nix251
-rw-r--r--nixos/modules/services/networking/miniupnpd.nix12
-rw-r--r--nixos/modules/services/networking/miredo.nix12
-rw-r--r--nixos/modules/services/networking/mjpg-streamer.nix12
-rw-r--r--nixos/modules/services/networking/mmsd.nix38
-rw-r--r--nixos/modules/services/networking/monero.nix46
-rw-r--r--nixos/modules/services/networking/morty.nix18
-rw-r--r--nixos/modules/services/networking/mosquitto.nix103
-rw-r--r--nixos/modules/services/networking/mozillavpn.nix9
-rw-r--r--nixos/modules/services/networking/mstpd.nix2
-rw-r--r--nixos/modules/services/networking/mtprotoproxy.nix14
-rw-r--r--nixos/modules/services/networking/mtr-exporter.nix12
-rw-r--r--nixos/modules/services/networking/mullvad-vpn.nix48
-rw-r--r--nixos/modules/services/networking/multipath.nix112
-rw-r--r--nixos/modules/services/networking/murmur.nix87
-rw-r--r--nixos/modules/services/networking/mxisd.nix31
-rw-r--r--nixos/modules/services/networking/namecoind.nix24
-rw-r--r--nixos/modules/services/networking/nar-serve.nix6
-rw-r--r--nixos/modules/services/networking/nat.nix39
-rw-r--r--nixos/modules/services/networking/nats.nix22
-rw-r--r--nixos/modules/services/networking/nbd.nix22
-rw-r--r--nixos/modules/services/networking/ncdns.nix68
-rw-r--r--nixos/modules/services/networking/ndppd.nix36
-rw-r--r--nixos/modules/services/networking/nebula.nix35
-rw-r--r--nixos/modules/services/networking/netbird.nix64
-rw-r--r--nixos/modules/services/networking/networkmanager.nix125
-rw-r--r--nixos/modules/services/networking/nextdns.nix4
-rw-r--r--nixos/modules/services/networking/nftables.nix19
-rw-r--r--nixos/modules/services/networking/nghttpx/backend-params-submodule.nix16
-rw-r--r--nixos/modules/services/networking/nghttpx/backend-submodule.nix6
-rw-r--r--nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix10
-rw-r--r--nixos/modules/services/networking/nghttpx/frontend-submodule.nix4
-rw-r--r--nixos/modules/services/networking/nghttpx/nghttpx-options.nix26
-rw-r--r--nixos/modules/services/networking/nghttpx/server-options.nix4
-rw-r--r--nixos/modules/services/networking/nghttpx/tls-submodule.nix4
-rw-r--r--nixos/modules/services/networking/ngircd.nix6
-rw-r--r--nixos/modules/services/networking/nix-serve.nix25
-rw-r--r--nixos/modules/services/networking/nix-store-gcs-proxy.nix8
-rw-r--r--nixos/modules/services/networking/nixops-dns.nix8
-rw-r--r--nixos/modules/services/networking/nntp-proxy.nix38
-rw-r--r--nixos/modules/services/networking/nomad.nix40
-rw-r--r--nixos/modules/services/networking/nsd.nix189
-rw-r--r--nixos/modules/services/networking/ntopng.nix22
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix26
-rw-r--r--nixos/modules/services/networking/ntp/ntpd.nix29
-rw-r--r--nixos/modules/services/networking/ntp/openntpd.nix8
-rw-r--r--nixos/modules/services/networking/nullidentdmod.nix4
-rw-r--r--nixos/modules/services/networking/nylon.nix24
-rw-r--r--nixos/modules/services/networking/ocserv.nix4
-rw-r--r--nixos/modules/services/networking/ofono.nix4
-rw-r--r--nixos/modules/services/networking/oidentd.nix2
-rw-r--r--nixos/modules/services/networking/onedrive.nix8
-rw-r--r--nixos/modules/services/networking/openconnect.nix34
-rw-r--r--nixos/modules/services/networking/openvpn.nix26
-rw-r--r--nixos/modules/services/networking/ostinato.nix16
-rw-r--r--nixos/modules/services/networking/owamp.nix2
-rw-r--r--nixos/modules/services/networking/pdns-recursor.nix42
-rw-r--r--nixos/modules/services/networking/pdnsd.nix16
-rw-r--r--nixos/modules/services/networking/pixiecore.nix26
-rw-r--r--nixos/modules/services/networking/pleroma.nix34
-rw-r--r--nixos/modules/services/networking/polipo.nix20
-rw-r--r--nixos/modules/services/networking/powerdns.nix6
-rw-r--r--nixos/modules/services/networking/pppd.nix14
-rw-r--r--nixos/modules/services/networking/pptpd.nix16
-rw-r--r--nixos/modules/services/networking/prayer.nix8
-rw-r--r--nixos/modules/services/networking/privoxy.nix60
-rw-r--r--nixos/modules/services/networking/prosody.nix266
-rw-r--r--nixos/modules/services/networking/quassel.nix20
-rw-r--r--nixos/modules/services/networking/quicktun.nix24
-rw-r--r--nixos/modules/services/networking/quorum.nix40
-rw-r--r--nixos/modules/services/networking/r53-ddns.nix72
-rw-r--r--nixos/modules/services/networking/radicale.nix30
-rw-r--r--nixos/modules/services/networking/radvd.nix23
-rw-r--r--nixos/modules/services/networking/rdnssd.nix6
-rw-r--r--nixos/modules/services/networking/redsocks.nix34
-rw-r--r--nixos/modules/services/networking/resilio.nix94
-rw-r--r--nixos/modules/services/networking/robustirc-bridge.nix4
-rw-r--r--nixos/modules/services/networking/routedns.nix84
-rw-r--r--nixos/modules/services/networking/rpcbind.nix12
-rw-r--r--nixos/modules/services/networking/rxe.nix6
-rw-r--r--nixos/modules/services/networking/sabnzbd.nix12
-rw-r--r--nixos/modules/services/networking/seafile.nix50
-rw-r--r--nixos/modules/services/networking/searx.nix68
-rw-r--r--nixos/modules/services/networking/shadowsocks.nix28
-rw-r--r--nixos/modules/services/networking/shairport-sync.nix10
-rw-r--r--nixos/modules/services/networking/shellhub-agent.nix12
-rw-r--r--nixos/modules/services/networking/shorewall.nix19
-rw-r--r--nixos/modules/services/networking/shorewall6.nix19
-rw-r--r--nixos/modules/services/networking/shout.nix20
-rw-r--r--nixos/modules/services/networking/skydns.nix20
-rw-r--r--nixos/modules/services/networking/smartdns.nix10
-rw-r--r--nixos/modules/services/networking/smokeping.nix72
-rw-r--r--nixos/modules/services/networking/sniproxy.nix8
-rw-r--r--nixos/modules/services/networking/snowflake-proxy.nix12
-rw-r--r--nixos/modules/services/networking/softether.nix24
-rw-r--r--nixos/modules/services/networking/soju.nix38
-rw-r--r--nixos/modules/services/networking/solanum.nix12
-rw-r--r--nixos/modules/services/networking/spacecookie.nix32
-rw-r--r--nixos/modules/services/networking/spiped.nix58
-rw-r--r--nixos/modules/services/networking/squid.nix12
-rw-r--r--nixos/modules/services/networking/ssh/lshd.nix26
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix120
-rw-r--r--nixos/modules/services/networking/sslh.nix16
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/module.nix8
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix17
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix622
-rw-r--r--nixos/modules/services/networking/strongswan.nix26
-rw-r--r--nixos/modules/services/networking/stubby.nix36
-rw-r--r--nixos/modules/services/networking/stunnel.nix168
-rw-r--r--nixos/modules/services/networking/supplicant.nix66
-rw-r--r--nixos/modules/services/networking/supybot.nix16
-rw-r--r--nixos/modules/services/networking/syncplay.nix18
-rw-r--r--nixos/modules/services/networking/syncthing-relay.nix24
-rw-r--r--nixos/modules/services/networking/syncthing.nix196
-rw-r--r--nixos/modules/services/networking/tailscale.nix67
-rw-r--r--nixos/modules/services/networking/tayga.nix195
-rw-r--r--nixos/modules/services/networking/tcpcrypt.nix2
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix23
-rw-r--r--nixos/modules/services/networking/tedicross.nix12
-rw-r--r--nixos/modules/services/networking/teleport.nix26
-rw-r--r--nixos/modules/services/networking/tetrd.nix2
-rw-r--r--nixos/modules/services/networking/tftpd.nix4
-rw-r--r--nixos/modules/services/networking/thelounge.nix24
-rw-r--r--nixos/modules/services/networking/tinc.nix64
-rw-r--r--nixos/modules/services/networking/tinydns.nix6
-rw-r--r--nixos/modules/services/networking/tmate-ssh-server.nix122
-rw-r--r--nixos/modules/services/networking/tox-bootstrapd.nix14
-rw-r--r--nixos/modules/services/networking/tox-node.nix20
-rw-r--r--nixos/modules/services/networking/toxvpn.nix10
-rw-r--r--nixos/modules/services/networking/trickster.nix38
-rw-r--r--nixos/modules/services/networking/tvheadend.nix6
-rw-r--r--nixos/modules/services/networking/twingate.nix28
-rw-r--r--nixos/modules/services/networking/ucarp.nix36
-rw-r--r--nixos/modules/services/networking/unbound.nix33
-rw-r--r--nixos/modules/services/networking/unifi.nix26
-rw-r--r--nixos/modules/services/networking/uptermd.nix109
-rw-r--r--nixos/modules/services/networking/v2ray.nix30
-rw-r--r--nixos/modules/services/networking/v2raya.nix39
-rw-r--r--nixos/modules/services/networking/vdirsyncer.nix214
-rw-r--r--nixos/modules/services/networking/vsftpd.nix67
-rw-r--r--nixos/modules/services/networking/wasabibackend.nix26
-rw-r--r--nixos/modules/services/networking/websockify.nix8
-rw-r--r--nixos/modules/services/networking/wg-netmanager.nix2
-rw-r--r--nixos/modules/services/networking/wg-quick.nix90
-rw-r--r--nixos/modules/services/networking/wireguard.nix194
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix139
-rw-r--r--nixos/modules/services/networking/x2goserver.nix12
-rw-r--r--nixos/modules/services/networking/xandikos.nix18
-rw-r--r--nixos/modules/services/networking/xinetd.nix28
-rw-r--r--nixos/modules/services/networking/xl2tpd.nix18
-rw-r--r--nixos/modules/services/networking/xray.nix96
-rw-r--r--nixos/modules/services/networking/xrdp.nix20
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix100
-rw-r--r--nixos/modules/services/networking/yggdrasil.xml8
-rw-r--r--nixos/modules/services/networking/zerobin.nix14
-rw-r--r--nixos/modules/services/networking/zeronet.nix27
-rw-r--r--nixos/modules/services/networking/zerotierone.nix10
-rw-r--r--nixos/modules/services/networking/znc/default.nix63
-rw-r--r--nixos/modules/services/networking/znc/options.nix59
-rw-r--r--nixos/modules/services/printing/cupsd.nix60
-rw-r--r--nixos/modules/services/printing/ipp-usb.nix63
-rw-r--r--nixos/modules/services/scheduling/atd.nix12
-rw-r--r--nixos/modules/services/scheduling/cron.nix10
-rw-r--r--nixos/modules/services/scheduling/fcron.nix14
-rw-r--r--nixos/modules/services/search/elasticsearch-curator.nix12
-rw-r--r--nixos/modules/services/search/elasticsearch.nix30
-rw-r--r--nixos/modules/services/search/hound.nix17
-rw-r--r--nixos/modules/services/search/kibana.nix50
-rw-r--r--nixos/modules/services/search/meilisearch.nix22
-rw-r--r--nixos/modules/services/search/solr.nix16
-rw-r--r--nixos/modules/services/security/aesmd.nix22
-rw-r--r--nixos/modules/services/security/certmgr.nix36
-rw-r--r--nixos/modules/services/security/cfssl.nix58
-rw-r--r--nixos/modules/services/security/clamav.nix16
-rw-r--r--nixos/modules/services/security/endlessh-go.nix138
-rw-r--r--nixos/modules/services/security/endlessh.nix99
-rw-r--r--nixos/modules/services/security/fail2ban.nix67
-rw-r--r--nixos/modules/services/security/fprintd.nix8
-rw-r--r--nixos/modules/services/security/haka.nix22
-rw-r--r--nixos/modules/services/security/haveged.nix6
-rw-r--r--nixos/modules/services/security/hockeypuck.nix14
-rw-r--r--nixos/modules/services/security/hologram-agent.nix6
-rw-r--r--nixos/modules/services/security/hologram-server.nix30
-rw-r--r--nixos/modules/services/security/infnoise.nix60
-rw-r--r--nixos/modules/services/security/kanidm.nix355
-rw-r--r--nixos/modules/services/security/munge.nix4
-rw-r--r--nixos/modules/services/security/nginx-sso.nix8
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix116
-rw-r--r--nixos/modules/services/security/oauth2_proxy_nginx.nix4
-rw-r--r--nixos/modules/services/security/opensnitch.nix85
-rw-r--r--nixos/modules/services/security/pass-secret-service.nix27
-rw-r--r--nixos/modules/services/security/physlock.nix32
-rw-r--r--nixos/modules/services/security/privacyidea.nix207
-rw-r--r--nixos/modules/services/security/shibboleth-sp.nix10
-rw-r--r--nixos/modules/services/security/sks.nix16
-rw-r--r--nixos/modules/services/security/sshguard.nix18
-rw-r--r--nixos/modules/services/security/sslmate-agent.nix2
-rw-r--r--nixos/modules/services/security/step-ca.nix60
-rw-r--r--nixos/modules/services/security/tor.nix361
-rw-r--r--nixos/modules/services/security/torify.nix20
-rw-r--r--nixos/modules/services/security/torsocks.nix22
-rw-r--r--nixos/modules/services/security/usbguard.nix36
-rw-r--r--nixos/modules/services/security/vault.nix77
-rw-r--r--nixos/modules/services/security/vaultwarden/default.nix112
-rw-r--r--nixos/modules/services/security/yubikey-agent.nix4
-rw-r--r--nixos/modules/services/system/automatic-timezoned.nix92
-rw-r--r--nixos/modules/services/system/cachix-agent/default.nix38
-rw-r--r--nixos/modules/services/system/cachix-watch-store.nix87
-rw-r--r--nixos/modules/services/system/cloud-init.nix15
-rw-r--r--nixos/modules/services/system/dbus.nix196
-rw-r--r--nixos/modules/services/system/earlyoom.nix48
-rw-r--r--nixos/modules/services/system/kerberos/default.nix12
-rw-r--r--nixos/modules/services/system/kerberos/mit.nix2
-rw-r--r--nixos/modules/services/system/localtime.nix49
-rw-r--r--nixos/modules/services/system/localtimed.nix66
-rw-r--r--nixos/modules/services/system/nscd.nix93
-rw-r--r--nixos/modules/services/system/saslauthd.nix8
-rw-r--r--nixos/modules/services/system/self-deploy.nix20
-rw-r--r--nixos/modules/services/system/systembus-notify.nix4
-rw-r--r--nixos/modules/services/system/uptimed.nix4
-rw-r--r--nixos/modules/services/torrent/deluge.nix60
-rw-r--r--nixos/modules/services/torrent/flexget.nix12
-rw-r--r--nixos/modules/services/torrent/magnetico.nix56
-rw-r--r--nixos/modules/services/torrent/opentracker.nix6
-rw-r--r--nixos/modules/services/torrent/peerflix.nix6
-rw-r--r--nixos/modules/services/torrent/rtorrent.nix24
-rw-r--r--nixos/modules/services/torrent/transmission.nix146
-rw-r--r--nixos/modules/services/tracing/tempo.nix68
-rw-r--r--nixos/modules/services/ttys/getty.nix23
-rw-r--r--nixos/modules/services/ttys/gpm.nix4
-rw-r--r--nixos/modules/services/ttys/kmscon.nix16
-rw-r--r--nixos/modules/services/video/epgstation/default.nix50
-rw-r--r--nixos/modules/services/video/mirakurun.nix39
-rw-r--r--nixos/modules/services/video/replay-sorcery.nix10
-rw-r--r--nixos/modules/services/video/rtsp-simple-server.nix8
-rw-r--r--nixos/modules/services/video/unifi-video.nix22
-rw-r--r--nixos/modules/services/wayland/cage.nix10
-rw-r--r--nixos/modules/services/web-apps/alps.nix132
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix99
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix67
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix88
-rw-r--r--nixos/modules/services/web-apps/baget.nix8
-rw-r--r--nixos/modules/services/web-apps/bookstack.nix85
-rw-r--r--nixos/modules/services/web-apps/calibre-web.nix28
-rw-r--r--nixos/modules/services/web-apps/changedetection-io.nix220
-rw-r--r--nixos/modules/services/web-apps/code-server.nix32
-rw-r--r--nixos/modules/services/web-apps/convos.nix12
-rw-r--r--nixos/modules/services/web-apps/cryptpad.nix54
-rw-r--r--nixos/modules/services/web-apps/dex.nix36
-rw-r--r--nixos/modules/services/web-apps/discourse.nix164
-rw-r--r--nixos/modules/services/web-apps/documize.nix65
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix103
-rw-r--r--nixos/modules/services/web-apps/dolibarr.nix323
-rw-r--r--nixos/modules/services/web-apps/engelsystem.nix12
-rw-r--r--nixos/modules/services/web-apps/ethercalc.nix10
-rw-r--r--nixos/modules/services/web-apps/fluidd.nix8
-rw-r--r--nixos/modules/services/web-apps/freshrss.nix282
-rw-r--r--nixos/modules/services/web-apps/galene.nix57
-rw-r--r--nixos/modules/services/web-apps/gerrit.nix32
-rw-r--r--nixos/modules/services/web-apps/gotify-server.nix8
-rw-r--r--nixos/modules/services/web-apps/grocy.nix26
-rw-r--r--nixos/modules/services/web-apps/healthchecks.nix249
-rw-r--r--nixos/modules/services/web-apps/hedgedoc.nix372
-rw-r--r--nixos/modules/services/web-apps/hledger-web.nix24
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix32
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix38
-rw-r--r--nixos/modules/services/web-apps/ihatemoney/default.nix30
-rw-r--r--nixos/modules/services/web-apps/invidious.nix40
-rw-r--r--nixos/modules/services/web-apps/invoiceplane.nix109
-rw-r--r--nixos/modules/services/web-apps/isso.nix32
-rw-r--r--nixos/modules/services/web-apps/jirafeau.nix30
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix80
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix201
-rw-r--r--nixos/modules/services/web-apps/komga.nix99
-rw-r--r--nixos/modules/services/web-apps/lemmy.md3
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix84
-rw-r--r--nixos/modules/services/web-apps/lemmy.xml7
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix36
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix317
-rw-r--r--nixos/modules/services/web-apps/matomo.nix30
-rw-r--r--nixos/modules/services/web-apps/mattermost.nix44
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix61
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix21
-rw-r--r--nixos/modules/services/web-apps/moodle.nix47
-rw-r--r--nixos/modules/services/web-apps/netbox.nix43
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix392
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml18
-rw-r--r--nixos/modules/services/web-apps/nexus.nix16
-rw-r--r--nixos/modules/services/web-apps/nifi.nix26
-rw-r--r--nixos/modules/services/web-apps/node-red.nix25
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix291
-rw-r--r--nixos/modules/services/web-apps/openwebrx.nix8
-rw-r--r--nixos/modules/services/web-apps/outline.nix788
-rw-r--r--nixos/modules/services/web-apps/peering-manager.nix265
-rw-r--r--nixos/modules/services/web-apps/peertube.nix388
-rw-r--r--nixos/modules/services/web-apps/pgpkeyserver-lite.nix20
-rw-r--r--nixos/modules/services/web-apps/phylactery.nix51
-rw-r--r--nixos/modules/services/web-apps/pict-rs.nix8
-rw-r--r--nixos/modules/services/web-apps/plantuml-server.nix28
-rw-r--r--nixos/modules/services/web-apps/plausible.nix68
-rw-r--r--nixos/modules/services/web-apps/powerdns-admin.nix12
-rw-r--r--nixos/modules/services/web-apps/prosody-filer.nix8
-rw-r--r--nixos/modules/services/web-apps/restya-board.nix52
-rw-r--r--nixos/modules/services/web-apps/rss-bridge.nix18
-rw-r--r--nixos/modules/services/web-apps/selfoss.nix20
-rw-r--r--nixos/modules/services/web-apps/shiori.nix10
-rw-r--r--nixos/modules/services/web-apps/snipe-it.nix509
-rw-r--r--nixos/modules/services/web-apps/sogo.nix16
-rw-r--r--nixos/modules/services/web-apps/timetagger.nix80
-rw-r--r--nixos/modules/services/web-apps/trilium.nix29
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix91
-rw-r--r--nixos/modules/services/web-apps/vikunja.nix36
-rw-r--r--nixos/modules/services/web-apps/virtlyst.nix73
-rw-r--r--nixos/modules/services/web-apps/whitebophir.nix8
-rw-r--r--nixos/modules/services/web-apps/wiki-js.nix51
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix103
-rw-r--r--nixos/modules/services/web-apps/writefreely.nix485
-rw-r--r--nixos/modules/services/web-apps/youtrack.nix24
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix42
-rw-r--r--nixos/modules/services/web-servers/agate.nix20
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix88
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/location-options.nix16
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/vhost-options.nix108
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix145
-rw-r--r--nixos/modules/services/web-servers/caddy/vhost-options.nix22
-rw-r--r--nixos/modules/services/web-servers/darkhttpd.nix12
-rw-r--r--nixos/modules/services/web-servers/fcgiwrap.nix12
-rw-r--r--nixos/modules/services/web-servers/garage.nix91
-rw-r--r--nixos/modules/services/web-servers/hitch/default.nix20
-rw-r--r--nixos/modules/services/web-servers/hydron.nix23
-rw-r--r--nixos/modules/services/web-servers/jboss/default.nix16
-rw-r--r--nixos/modules/services/web-servers/keter/bundle.nix40
-rw-r--r--nixos/modules/services/web-servers/keter/default.nix162
-rw-r--r--nixos/modules/services/web-servers/lighttpd/cgit.nix6
-rw-r--r--nixos/modules/services/web-servers/lighttpd/collectd.nix8
-rw-r--r--nixos/modules/services/web-servers/lighttpd/default.nix28
-rw-r--r--nixos/modules/services/web-servers/lighttpd/gitweb.nix2
-rw-r--r--nixos/modules/services/web-servers/merecat.nix55
-rw-r--r--nixos/modules/services/web-servers/mighttpd2.nix8
-rw-r--r--nixos/modules/services/web-servers/minio.nix28
-rw-r--r--nixos/modules/services/web-servers/molly-brown.nix20
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix301
-rw-r--r--nixos/modules/services/web-servers/nginx/gitweb.nix12
-rw-r--r--nixos/modules/services/web-servers/nginx/location-options.nix37
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix108
-rw-r--r--nixos/modules/services/web-servers/phpfpm/default.nix55
-rw-r--r--nixos/modules/services/web-servers/pomerium.nix14
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix50
-rw-r--r--nixos/modules/services/web-servers/traefik.nix22
-rw-r--r--nixos/modules/services/web-servers/trafficserver/default.nix94
-rw-r--r--nixos/modules/services/web-servers/ttyd.nix44
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix14
-rw-r--r--nixos/modules/services/web-servers/uwsgi.nix53
-rw-r--r--nixos/modules/services/web-servers/varnish/default.nix26
-rw-r--r--nixos/modules/services/web-servers/zope2.nix16
-rw-r--r--nixos/modules/services/x11/clight.nix20
-rw-r--r--nixos/modules/services/x11/colord.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/cde.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix78
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix32
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix7
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix153
-rw-r--r--nixos/modules/services/x11/desktop-managers/kodi.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/lumina.nix6
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix13
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix7
-rw-r--r--nixos/modules/services/x11/desktop-managers/none.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix90
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.xml6
-rw-r--r--nixos/modules/services/x11/desktop-managers/phosh.nix (renamed from nixos/modules/programs/phosh.nix)97
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix316
-rw-r--r--nixos/modules/services/x11/desktop-managers/retroarch.nix6
-rw-r--r--nixos/modules/services/x11/desktop-managers/surf-display.nix20
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix33
-rw-r--r--nixos/modules/services/x11/desktop-managers/xterm.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix56
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix25
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix20
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix24
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix8
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/mobile.nix26
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix149
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix10
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix21
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix39
-rw-r--r--nixos/modules/services/x11/display-managers/startx.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/sx.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/xpra.nix19
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix30
-rw-r--r--nixos/modules/services/x11/fractalart.nix6
-rw-r--r--nixos/modules/services/x11/gdk-pixbuf.nix31
-rw-r--r--nixos/modules/services/x11/hardware/cmt.nix6
-rw-r--r--nixos/modules/services/x11/hardware/digimend.nix2
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix72
-rw-r--r--nixos/modules/services/x11/hardware/synaptics.nix38
-rw-r--r--nixos/modules/services/x11/hardware/wacom.nix4
-rw-r--r--nixos/modules/services/x11/imwheel.nix12
-rw-r--r--nixos/modules/services/x11/picom.nix92
-rw-r--r--nixos/modules/services/x11/redshift.nix26
-rw-r--r--nixos/modules/services/x11/touchegg.nix4
-rw-r--r--nixos/modules/services/x11/unclutter-xfixes.nix10
-rw-r--r--nixos/modules/services/x11/unclutter.nix14
-rw-r--r--nixos/modules/services/x11/urserver.nix2
-rw-r--r--nixos/modules/services/x11/urxvtd.nix4
-rw-r--r--nixos/modules/services/x11/window-managers/2bwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/afterstep.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/awesome.nix10
-rw-r--r--nixos/modules/services/x11/window-managers/berry.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/bspwm.nix10
-rw-r--r--nixos/modules/services/x11/window-managers/clfswm.nix4
-rw-r--r--nixos/modules/services/x11/window-managers/cwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/default.nix13
-rw-r--r--nixos/modules/services/x11/window-managers/dwm.nix24
-rw-r--r--nixos/modules/services/x11/window-managers/e16.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/evilwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/exwm.nix10
-rw-r--r--nixos/modules/services/x11/window-managers/fluxbox.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/fvwm.nix41
-rw-r--r--nixos/modules/services/x11/window-managers/fvwm2.nix47
-rw-r--r--nixos/modules/services/x11/window-managers/fvwm3.nix35
-rw-r--r--nixos/modules/services/x11/window-managers/hackedbox.nix25
-rw-r--r--nixos/modules/services/x11/window-managers/herbstluftwm.nix6
-rw-r--r--nixos/modules/services/x11/window-managers/hypr.nix25
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix10
-rw-r--r--nixos/modules/services/x11/window-managers/icewm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/jwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/katriawm.nix27
-rw-r--r--nixos/modules/services/x11/window-managers/leftwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/lwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/metacity.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/mlvwm.nix4
-rw-r--r--nixos/modules/services/x11/window-managers/mwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/notion.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/openbox.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/oroborus.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/pekwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/qtile.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/ratpoison.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/sawfish.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/smallwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/spectrwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/stumpwm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/tinywm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/twm.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/windowlab.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/windowmaker.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/wmderland.nix6
-rw-r--r--nixos/modules/services/x11/window-managers/wmii.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix69
-rw-r--r--nixos/modules/services/x11/window-managers/yeahwm.nix2
-rw-r--r--nixos/modules/services/x11/xautolock.nix30
-rw-r--r--nixos/modules/services/x11/xbanish.nix4
-rw-r--r--nixos/modules/services/x11/xfs.nix2
-rw-r--r--nixos/modules/services/x11/xserver.nix140
-rw-r--r--nixos/modules/system/activation/activation-script.nix26
-rw-r--r--nixos/modules/system/activation/bootspec.cue18
-rw-r--r--nixos/modules/system/activation/bootspec.nix126
-rw-r--r--nixos/modules/system/activation/specialisation.nix85
-rwxr-xr-xnixos/modules/system/activation/switch-to-configuration.pl28
-rw-r--r--nixos/modules/system/activation/test.nix27
-rw-r--r--nixos/modules/system/activation/top-level.nix178
-rw-r--r--nixos/modules/system/boot/binfmt.nix27
-rw-r--r--nixos/modules/system/boot/emergency-mode.nix4
-rw-r--r--nixos/modules/system/boot/grow-partition.nix2
-rw-r--r--nixos/modules/system/boot/initrd-network.nix21
-rw-r--r--nixos/modules/system/boot/initrd-openvpn.nix16
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix67
-rw-r--r--nixos/modules/system/boot/kernel.nix65
-rw-r--r--nixos/modules/system/boot/kernel_config.nix10
-rw-r--r--nixos/modules/system/boot/loader/efi.nix4
-rw-r--r--nixos/modules/system/boot/loader/external/external.md26
-rw-r--r--nixos/modules/system/boot/loader/external/external.nix38
-rw-r--r--nixos/modules/system/boot/loader/external/external.xml41
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix10
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix14
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix215
-rw-r--r--nixos/modules/system/boot/loader/grub/ipxe.nix2
-rw-r--r--nixos/modules/system/boot/loader/grub/memtest.nix58
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script.nix2
-rw-r--r--nixos/modules/system/boot/loader/loader.nix2
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix18
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py51
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix110
-rw-r--r--nixos/modules/system/boot/luksroot.nix125
-rw-r--r--nixos/modules/system/boot/modprobe.nix16
-rw-r--r--nixos/modules/system/boot/networkd.nix518
-rw-r--r--nixos/modules/system/boot/plymouth.nix127
-rw-r--r--nixos/modules/system/boot/resolved.nix63
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh21
-rw-r--r--nixos/modules/system/boot/stage-1.nix119
-rwxr-xr-xnixos/modules/system/boot/stage-2-init.sh2
-rw-r--r--nixos/modules/system/boot/stage-2.nix20
-rw-r--r--nixos/modules/system/boot/systemd.nix115
-rw-r--r--nixos/modules/system/boot/systemd/coredump.nix59
-rw-r--r--nixos/modules/system/boot/systemd/initrd-secrets.nix36
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix165
-rw-r--r--nixos/modules/system/boot/systemd/journald.nix20
-rw-r--r--nixos/modules/system/boot/systemd/logind.nix26
-rw-r--r--nixos/modules/system/boot/systemd/nspawn.nix33
-rw-r--r--nixos/modules/system/boot/systemd/oomd.nix57
-rw-r--r--nixos/modules/system/boot/systemd/shutdown.nix56
-rw-r--r--nixos/modules/system/boot/systemd/tmpfiles.nix15
-rw-r--r--nixos/modules/system/boot/systemd/user.nix23
-rw-r--r--nixos/modules/system/boot/timesyncd.nix10
-rw-r--r--nixos/modules/system/boot/tmp.nix10
-rw-r--r--nixos/modules/system/boot/uvesafb.nix39
-rw-r--r--nixos/modules/system/build.nix2
-rw-r--r--nixos/modules/system/etc/etc.nix30
-rw-r--r--nixos/modules/system/etc/setup-etc.pl2
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix63
-rw-r--r--nixos/modules/tasks/bcache.nix2
-rw-r--r--nixos/modules/tasks/cpu-freq.nix6
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix14
-rw-r--r--nixos/modules/tasks/filesystems.nix114
-rw-r--r--nixos/modules/tasks/filesystems/btrfs.nix14
-rw-r--r--nixos/modules/tasks/filesystems/ext.nix3
-rw-r--r--nixos/modules/tasks/filesystems/jfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/nfs.nix6
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix309
-rw-r--r--nixos/modules/tasks/lvm.nix22
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix11
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix58
-rw-r--r--nixos/modules/tasks/network-interfaces.nix353
-rw-r--r--nixos/modules/tasks/powertop.nix2
-rw-r--r--nixos/modules/tasks/scsi-link-power-management.nix4
-rw-r--r--nixos/modules/tasks/snapraid.nix26
-rw-r--r--nixos/modules/tasks/stratis.nix18
-rw-r--r--nixos/modules/tasks/swraid.nix4
-rw-r--r--nixos/modules/tasks/trackpoint.nix12
-rw-r--r--nixos/modules/testing/service-runner.nix2
-rw-r--r--nixos/modules/testing/test-instrumentation.nix48
-rw-r--r--nixos/modules/virtualisation/amazon-ec2-amis.nix98
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix101
-rw-r--r--nixos/modules/virtualisation/amazon-init.nix4
-rw-r--r--nixos/modules/virtualisation/amazon-options.nix20
-rw-r--r--nixos/modules/virtualisation/anbox.nix17
-rw-r--r--nixos/modules/virtualisation/appvm.nix49
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix8
-rw-r--r--nixos/modules/virtualisation/azure-image.nix2
-rw-r--r--nixos/modules/virtualisation/build-vm.nix8
-rw-r--r--nixos/modules/virtualisation/container-config.nix14
-rw-r--r--nixos/modules/virtualisation/containerd.nix8
-rw-r--r--nixos/modules/virtualisation/containers.nix44
-rw-r--r--nixos/modules/virtualisation/cri-o.nix30
-rw-r--r--nixos/modules/virtualisation/digital-ocean-config.nix6
-rw-r--r--nixos/modules/virtualisation/digital-ocean-image.nix12
-rw-r--r--nixos/modules/virtualisation/digital-ocean-init.nix12
-rw-r--r--nixos/modules/virtualisation/docker-rootless.nix13
-rw-r--r--nixos/modules/virtualisation/docker.nix44
-rw-r--r--nixos/modules/virtualisation/ec2-data.nix1
-rw-r--r--nixos/modules/virtualisation/ec2-metadata-fetcher.nix77
-rw-r--r--nixos/modules/virtualisation/ec2-metadata-fetcher.sh67
-rw-r--r--nixos/modules/virtualisation/ecs-agent.nix6
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix6
-rw-r--r--nixos/modules/virtualisation/hyperv-guest.nix6
-rw-r--r--nixos/modules/virtualisation/hyperv-image.nix6
-rw-r--r--nixos/modules/virtualisation/includes-to-excludes.py86
-rw-r--r--nixos/modules/virtualisation/kvmgt.nix16
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix94
-rw-r--r--nixos/modules/virtualisation/linode-config.nix75
-rw-r--r--nixos/modules/virtualisation/linode-image.nix66
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix69
-rw-r--r--nixos/modules/virtualisation/lxc.nix18
-rw-r--r--nixos/modules/virtualisation/lxcfs.nix8
-rw-r--r--nixos/modules/virtualisation/lxd.nix34
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix157
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix135
-rw-r--r--nixos/modules/virtualisation/openstack-options.nix10
-rw-r--r--nixos/modules/virtualisation/openvswitch.nix8
-rw-r--r--nixos/modules/virtualisation/parallels-guest.nix67
-rw-r--r--nixos/modules/virtualisation/podman/default.nix58
-rw-r--r--nixos/modules/virtualisation/podman/dnsname.nix2
-rw-r--r--nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix2
-rw-r--r--nixos/modules/virtualisation/podman/network-socket.nix22
-rw-r--r--nixos/modules/virtualisation/proxmox-image.nix133
-rw-r--r--nixos/modules/virtualisation/proxmox-lxc.nix15
-rw-r--r--nixos/modules/virtualisation/qemu-guest-agent.nix4
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix338
-rw-r--r--nixos/modules/virtualisation/railcar.nix124
-rw-r--r--nixos/modules/virtualisation/rosetta.nix73
-rw-r--r--nixos/modules/virtualisation/spice-usb-redirection.nix2
-rw-r--r--nixos/modules/virtualisation/virtualbox-guest.nix4
-rw-r--r--nixos/modules/virtualisation/virtualbox-host.nix62
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix28
-rw-r--r--nixos/modules/virtualisation/vmware-guest.nix8
-rw-r--r--nixos/modules/virtualisation/vmware-host.nix166
-rw-r--r--nixos/modules/virtualisation/vmware-image.nix10
-rw-r--r--nixos/modules/virtualisation/waydroid.nix4
-rw-r--r--nixos/modules/virtualisation/xe-guest-utilities.nix2
-rw-r--r--nixos/modules/virtualisation/xen-dom0.nix32
-rw-r--r--nixos/release-combined.nix20
-rw-r--r--nixos/release-small.nix86
-rw-r--r--nixos/release.nix86
-rw-r--r--nixos/tests/3proxy.nix14
-rw-r--r--nixos/tests/acme.nix140
-rw-r--r--nixos/tests/adguardhome.nix22
-rw-r--r--nixos/tests/aesmd.nix4
-rw-r--r--nixos/tests/airsonic.nix3
-rw-r--r--nixos/tests/all-tests.nix201
-rw-r--r--nixos/tests/alps.nix108
-rw-r--r--nixos/tests/atuin.nix65
-rw-r--r--nixos/tests/auth-mysql.nix177
-rw-r--r--nixos/tests/bazarr.nix2
-rw-r--r--nixos/tests/bitcoind.nix4
-rw-r--r--nixos/tests/bootspec.nix170
-rw-r--r--nixos/tests/borgbackup.nix20
-rw-r--r--nixos/tests/btrbk-no-timer.nix37
-rw-r--r--nixos/tests/btrbk-section-order.nix51
-rw-r--r--nixos/tests/caddy.nix8
-rw-r--r--nixos/tests/cagebreak.nix1
-rw-r--r--nixos/tests/chromium.nix25
-rw-r--r--nixos/tests/cinnamon.nix68
-rw-r--r--nixos/tests/cloud-init-hostname.nix46
-rw-r--r--nixos/tests/cloud-init.nix19
-rw-r--r--nixos/tests/cockroachdb.nix2
-rw-r--r--nixos/tests/common/acme/client/default.nix4
-rw-r--r--nixos/tests/common/acme/server/acme.test.cert.pem32
-rw-r--r--nixos/tests/common/acme/server/acme.test.key.pem50
-rw-r--r--nixos/tests/common/acme/server/ca.cert.pem34
-rw-r--r--nixos/tests/common/acme/server/ca.key.pem50
-rw-r--r--nixos/tests/common/acme/server/default.nix16
-rw-r--r--nixos/tests/common/acme/server/generate-certs.nix6
-rw-r--r--nixos/tests/common/auto.nix6
-rw-r--r--nixos/tests/common/ec2.nix2
-rw-r--r--nixos/tests/common/lxd/config.yaml24
-rw-r--r--nixos/tests/common/resolver.nix6
-rw-r--r--nixos/tests/containers-custom-pkgs.nix2
-rw-r--r--nixos/tests/containers-ephemeral.nix6
-rw-r--r--nixos/tests/containers-imperative.nix15
-rw-r--r--nixos/tests/containers-tmpfs.nix10
-rw-r--r--nixos/tests/containers-unified-hierarchy.nix21
-rw-r--r--nixos/tests/convos.nix4
-rw-r--r--nixos/tests/corerad.nix1
-rw-r--r--nixos/tests/couchdb.nix2
-rw-r--r--nixos/tests/cri-o.nix2
-rw-r--r--nixos/tests/cryptpad.nix18
-rw-r--r--nixos/tests/custom-ca.nix196
-rw-r--r--nixos/tests/deluge.nix6
-rw-r--r--nixos/tests/dhparams.nix138
-rw-r--r--nixos/tests/discourse.nix1
-rw-r--r--nixos/tests/dnscrypt-proxy2.nix8
-rw-r--r--nixos/tests/docker-edge.nix49
-rw-r--r--nixos/tests/docker-registry.nix2
-rw-r--r--nixos/tests/docker-tools-cross.nix6
-rw-r--r--nixos/tests/docker-tools.nix83
-rw-r--r--nixos/tests/docker.nix1
-rw-r--r--nixos/tests/documize.nix6
-rw-r--r--nixos/tests/dolibarr.nix59
-rw-r--r--nixos/tests/domination.nix2
-rw-r--r--nixos/tests/ec2.nix2
-rw-r--r--nixos/tests/ecryptfs.nix10
-rw-r--r--nixos/tests/endlessh-go.nix58
-rw-r--r--nixos/tests/endlessh.nix43
-rw-r--r--nixos/tests/evcc.nix97
-rw-r--r--nixos/tests/extra-python-packages.nix13
-rw-r--r--nixos/tests/fcitx/default.nix1
-rw-r--r--nixos/tests/firefox.nix25
-rw-r--r--nixos/tests/freenet.nix19
-rw-r--r--nixos/tests/freeswitch.nix2
-rw-r--r--nixos/tests/freshrss.nix19
-rw-r--r--nixos/tests/fscrypt.nix50
-rw-r--r--nixos/tests/garage.nix169
-rw-r--r--nixos/tests/ghostunnel.nix1
-rw-r--r--nixos/tests/gitea.nix2
-rw-r--r--nixos/tests/gitlab.nix325
-rw-r--r--nixos/tests/gitolite.nix2
-rw-r--r--nixos/tests/gocd-agent.nix2
-rw-r--r--nixos/tests/gollum.nix14
-rw-r--r--nixos/tests/grafana-agent.nix32
-rw-r--r--nixos/tests/grafana/basic.nix (renamed from nixos/tests/grafana.nix)71
-rw-r--r--nixos/tests/grafana/default.nix9
-rw-r--r--nixos/tests/grafana/provision/contact-points.yaml9
-rw-r--r--nixos/tests/grafana/provision/dashboards.yaml6
-rw-r--r--nixos/tests/grafana/provision/datasources.yaml7
-rw-r--r--nixos/tests/grafana/provision/default.nix251
-rw-r--r--nixos/tests/grafana/provision/mute-timings.yaml4
-rw-r--r--nixos/tests/grafana/provision/policies.yaml4
-rw-r--r--nixos/tests/grafana/provision/rules.yaml36
-rw-r--r--nixos/tests/grafana/provision/templates.yaml5
-rw-r--r--nixos/tests/grafana/provision/test_dashboard.json47
-rw-r--r--nixos/tests/graphite.nix14
-rw-r--r--nixos/tests/grocy.nix26
-rw-r--r--nixos/tests/hadoop/default.nix1
-rw-r--r--nixos/tests/hadoop/hbase.nix84
-rw-r--r--nixos/tests/hadoop/yarn.nix2
-rw-r--r--nixos/tests/hardened.nix9
-rw-r--r--nixos/tests/hbase.nix4
-rw-r--r--nixos/tests/headscale.nix17
-rw-r--r--nixos/tests/hedgedoc.nix4
-rw-r--r--nixos/tests/hibernate.nix5
-rw-r--r--nixos/tests/home-assistant.nix58
-rw-r--r--nixos/tests/hydra/common.nix2
-rw-r--r--nixos/tests/hydra/default.nix2
-rw-r--r--nixos/tests/ihatemoney/default.nix9
-rw-r--r--nixos/tests/installed-tests/default.nix10
-rw-r--r--nixos/tests/installed-tests/flatpak-builder.nix3
-rw-r--r--nixos/tests/installed-tests/flatpak.nix2
-rw-r--r--nixos/tests/installed-tests/gdk-pixbuf.nix2
-rw-r--r--nixos/tests/installed-tests/geocode-glib.nix13
-rw-r--r--nixos/tests/installed-tests/ibus.nix1
-rw-r--r--nixos/tests/installed-tests/json-glib.nix5
-rw-r--r--nixos/tests/installed-tests/librsvg.nix9
-rw-r--r--nixos/tests/installed-tests/power-profiles-daemon.nix9
-rw-r--r--nixos/tests/installer-systemd-stage-1.nix34
-rw-r--r--nixos/tests/installer.nix119
-rw-r--r--nixos/tests/invoiceplane.nix16
-rw-r--r--nixos/tests/isso.nix2
-rw-r--r--nixos/tests/jellyfin.nix24
-rw-r--r--nixos/tests/jenkins.nix19
-rw-r--r--nixos/tests/jibri.nix3
-rw-r--r--nixos/tests/jitsi-meet.nix3
-rw-r--r--nixos/tests/k3s-single-node-docker.nix84
-rw-r--r--nixos/tests/k3s/default.nix9
-rw-r--r--nixos/tests/k3s/multi-node.nix183
-rw-r--r--nixos/tests/k3s/single-node.nix (renamed from nixos/tests/k3s-single-node.nix)26
-rw-r--r--nixos/tests/kafka.nix17
-rw-r--r--nixos/tests/kanidm.nix74
-rw-r--r--nixos/tests/karma.nix84
-rw-r--r--nixos/tests/kea.nix2
-rw-r--r--nixos/tests/keepassxc.nix4
-rw-r--r--nixos/tests/kerberos/mit.nix2
-rw-r--r--nixos/tests/kernel-generic.nix1
-rw-r--r--nixos/tests/keter.nix42
-rw-r--r--nixos/tests/kexec.nix7
-rw-r--r--nixos/tests/keycloak.nix17
-rw-r--r--nixos/tests/komga.nix22
-rw-r--r--nixos/tests/krb5/example-config.nix2
-rw-r--r--nixos/tests/kthxbye.nix110
-rw-r--r--nixos/tests/kubernetes/base.nix4
-rw-r--r--nixos/tests/kubernetes/default.nix2
-rw-r--r--nixos/tests/kubernetes/dns.nix14
-rw-r--r--nixos/tests/kubernetes/e2e.nix40
-rw-r--r--nixos/tests/kubernetes/rbac.nix6
-rw-r--r--nixos/tests/kubo.nix (renamed from nixos/tests/ipfs.nix)23
-rw-r--r--nixos/tests/ladybird.nix30
-rw-r--r--nixos/tests/languagetool.nix19
-rw-r--r--nixos/tests/lemmy.nix89
-rw-r--r--nixos/tests/libreddit.nix14
-rw-r--r--nixos/tests/libuiohook.nix21
-rw-r--r--nixos/tests/libvirtd.nix61
-rw-r--r--nixos/tests/lidarr.nix2
-rw-r--r--nixos/tests/lighttpd.nix21
-rw-r--r--nixos/tests/listmonk.nix69
-rw-r--r--nixos/tests/login.nix6
-rw-r--r--nixos/tests/logrotate.nix29
-rw-r--r--nixos/tests/lorri/default.nix2
-rw-r--r--nixos/tests/lxd-image-server.nix63
-rw-r--r--nixos/tests/lxd-image.nix89
-rw-r--r--nixos/tests/lxd.nix103
-rw-r--r--nixos/tests/maddy.nix2
-rw-r--r--nixos/tests/mailcatcher.nix2
-rw-r--r--nixos/tests/mailhog.nix4
-rw-r--r--nixos/tests/make-test-python.nix2
-rw-r--r--nixos/tests/matomo.nix2
-rw-r--r--nixos/tests/matrix/appservice-irc.nix (renamed from nixos/tests/matrix-appservice-irc.nix)8
-rw-r--r--nixos/tests/matrix/conduit.nix (renamed from nixos/tests/matrix-conduit.nix)4
-rw-r--r--nixos/tests/matrix/dendrite.nix (renamed from nixos/tests/dendrite.nix)6
-rw-r--r--nixos/tests/matrix/mjolnir.nix2
-rw-r--r--nixos/tests/matrix/synapse.nix (renamed from nixos/tests/matrix-synapse.nix)6
-rw-r--r--nixos/tests/mediatomb.nix101
-rw-r--r--nixos/tests/meilisearch.nix19
-rw-r--r--nixos/tests/merecat.nix28
-rw-r--r--nixos/tests/mimir.nix50
-rw-r--r--nixos/tests/minecraft-server.nix3
-rw-r--r--nixos/tests/minidlna.nix33
-rw-r--r--nixos/tests/moodle.nix2
-rw-r--r--nixos/tests/mosquitto.nix38
-rw-r--r--nixos/tests/musescore.nix4
-rw-r--r--nixos/tests/mysql/common.nix7
-rw-r--r--nixos/tests/mysql/mysql-backup.nix1
-rw-r--r--nixos/tests/n8n.nix4
-rw-r--r--nixos/tests/navidrome.nix2
-rw-r--r--nixos/tests/ncdns.nix4
-rw-r--r--nixos/tests/neo4j.nix14
-rw-r--r--nixos/tests/netbird.nix21
-rw-r--r--nixos/tests/networking-proxy.nix2
-rw-r--r--nixos/tests/networking.nix92
-rw-r--r--nixos/tests/nextcloud/basic.nix7
-rw-r--r--nixos/tests/nextcloud/default.nix10
-rw-r--r--nixos/tests/nextcloud/openssl-sse.nix105
-rw-r--r--nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix117
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix5
-rw-r--r--nixos/tests/nghttpx.nix4
-rw-r--r--nixos/tests/nginx-auth.nix8
-rw-r--r--nixos/tests/nginx-etag.nix4
-rw-r--r--nixos/tests/nginx-globalredirect.nix24
-rw-r--r--nixos/tests/nginx-http3.nix93
-rw-r--r--nixos/tests/nginx-modsecurity.nix2
-rw-r--r--nixos/tests/nginx-njs.nix27
-rw-r--r--nixos/tests/nginx.nix2
-rw-r--r--nixos/tests/nitter.nix2
-rw-r--r--nixos/tests/nix-ld.nix7
-rw-r--r--nixos/tests/nixops/default.nix1
-rw-r--r--nixos/tests/node-red.nix2
-rw-r--r--nixos/tests/non-default-filesystems.nix54
-rw-r--r--nixos/tests/nscd.nix141
-rw-r--r--nixos/tests/ntfy-sh.nix21
-rw-r--r--nixos/tests/oci-containers.nix2
-rw-r--r--nixos/tests/ombi.nix2
-rw-r--r--nixos/tests/openldap.nix232
-rw-r--r--nixos/tests/openssh.nix18
-rw-r--r--nixos/tests/os-prober.nix25
-rw-r--r--nixos/tests/pam/pam-file-contents.nix1
-rw-r--r--nixos/tests/pam/pam-oath-login.nix24
-rw-r--r--nixos/tests/pam/pam-u2f.nix3
-rw-r--r--nixos/tests/paperless.nix10
-rw-r--r--nixos/tests/pass-secret-service.nix69
-rw-r--r--nixos/tests/patroni.nix206
-rw-r--r--nixos/tests/pdns-recursor.nix2
-rw-r--r--nixos/tests/pgadmin4-standalone.nix2
-rw-r--r--nixos/tests/pgadmin4.nix96
-rw-r--r--nixos/tests/phosh.nix70
-rw-r--r--nixos/tests/php/pcre.nix16
-rw-r--r--nixos/tests/pict-rs.nix2
-rw-r--r--nixos/tests/pinnwand.nix57
-rw-r--r--nixos/tests/plasma-bigscreen.nix38
-rw-r--r--nixos/tests/plasma5.nix8
-rw-r--r--nixos/tests/please.nix66
-rw-r--r--nixos/tests/pleroma.nix4
-rw-r--r--nixos/tests/plikd.nix2
-rw-r--r--nixos/tests/podgrab.nix4
-rw-r--r--nixos/tests/podman/default.nix73
-rw-r--r--nixos/tests/polaris.nix31
-rw-r--r--nixos/tests/postgresql.nix93
-rw-r--r--nixos/tests/powerdns.nix4
-rw-r--r--nixos/tests/pppd.nix2
-rw-r--r--nixos/tests/printing.nix1
-rw-r--r--nixos/tests/privacyidea.nix2
-rw-r--r--nixos/tests/privoxy.nix8
-rw-r--r--nixos/tests/prometheus-exporters.nix173
-rw-r--r--nixos/tests/prowlarr.nix2
-rw-r--r--nixos/tests/public-inbox.nix227
-rw-r--r--nixos/tests/pulseaudio.nix25
-rw-r--r--nixos/tests/pykms.nix14
-rw-r--r--nixos/tests/quake3.nix95
-rw-r--r--nixos/tests/rabbitmq.nix38
-rw-r--r--nixos/tests/radarr.nix2
-rw-r--r--nixos/tests/redis.nix2
-rw-r--r--nixos/tests/resolv.nix46
-rw-r--r--nixos/tests/restic.nix236
-rw-r--r--nixos/tests/retroarch.nix4
-rw-r--r--nixos/tests/riak.nix18
-rw-r--r--nixos/tests/sanoid.nix18
-rw-r--r--nixos/tests/schleuder.nix126
-rw-r--r--nixos/tests/seafile.nix12
-rw-r--r--nixos/tests/shadow.nix54
-rw-r--r--nixos/tests/signal-desktop.nix2
-rw-r--r--nixos/tests/smokeping.nix2
-rw-r--r--nixos/tests/sonarr.nix2
-rw-r--r--nixos/tests/sourcehut.nix44
-rw-r--r--nixos/tests/spark/default.nix1
-rw-r--r--nixos/tests/sqlite3-to-mysql.nix65
-rw-r--r--nixos/tests/sssd-ldap.nix13
-rw-r--r--nixos/tests/stratis/default.nix8
-rw-r--r--nixos/tests/stratis/encryption.nix32
-rw-r--r--nixos/tests/stratis/simple.nix39
-rw-r--r--nixos/tests/stunnel.nix174
-rw-r--r--nixos/tests/swap-partition.nix48
-rw-r--r--nixos/tests/sway.nix6
-rw-r--r--nixos/tests/switch-test.nix39
-rw-r--r--nixos/tests/sympa.nix2
-rw-r--r--nixos/tests/systemd-bpf.nix42
-rw-r--r--nixos/tests/systemd-confinement.nix2
-rw-r--r--nixos/tests/systemd-coredump.nix44
-rw-r--r--nixos/tests/systemd-cryptenroll.nix1
-rw-r--r--nixos/tests/systemd-initrd-luks-fido2.nix45
-rw-r--r--nixos/tests/systemd-initrd-luks-keyfile.nix2
-rw-r--r--nixos/tests/systemd-initrd-luks-password.nix5
-rw-r--r--nixos/tests/systemd-initrd-luks-tpm2.nix72
-rw-r--r--nixos/tests/systemd-initrd-modprobe.nix17
-rw-r--r--nixos/tests/systemd-initrd-simple.nix2
-rw-r--r--nixos/tests/systemd-machinectl.nix41
-rw-r--r--nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix125
-rw-r--r--nixos/tests/systemd-networkd-vrf.nix65
-rw-r--r--nixos/tests/systemd-no-tainted.nix14
-rw-r--r--nixos/tests/systemd-nspawn.nix19
-rw-r--r--nixos/tests/systemd-oomd.nix54
-rw-r--r--nixos/tests/systemd-portabled.nix51
-rw-r--r--nixos/tests/systemd-shutdown.nix11
-rw-r--r--nixos/tests/systemd.nix6
-rw-r--r--nixos/tests/tandoor-recipes.nix43
-rw-r--r--nixos/tests/tayga.nix235
-rw-r--r--nixos/tests/teleport.nix12
-rw-r--r--nixos/tests/terminal-emulators.nix9
-rw-r--r--nixos/tests/thelounge.nix2
-rw-r--r--nixos/tests/tmate-ssh-server.nix73
-rw-r--r--nixos/tests/tracee.nix49
-rw-r--r--nixos/tests/traefik.nix22
-rw-r--r--nixos/tests/upnp.nix4
-rw-r--r--nixos/tests/uptermd.nix65
-rw-r--r--nixos/tests/uptime-kuma.nix19
-rw-r--r--nixos/tests/user-activation-scripts.nix4
-rw-r--r--nixos/tests/user-home-mode.nix27
-rw-r--r--nixos/tests/v2ray.nix12
-rw-r--r--nixos/tests/varnish.nix55
-rw-r--r--nixos/tests/vault-dev.nix35
-rw-r--r--nixos/tests/vaultwarden.nix50
-rw-r--r--nixos/tests/vector.nix2
-rw-r--r--nixos/tests/vengi-tools.nix4
-rw-r--r--nixos/tests/vikunja.nix4
-rw-r--r--nixos/tests/virtualbox.nix35
-rw-r--r--nixos/tests/vscodium.nix14
-rw-r--r--nixos/tests/vsftpd.nix2
-rw-r--r--nixos/tests/warzone2100.nix26
-rw-r--r--nixos/tests/web-apps/healthchecks.nix42
-rw-r--r--nixos/tests/web-apps/mastodon.nix170
-rw-r--r--nixos/tests/web-apps/mastodon/default.nix9
-rw-r--r--nixos/tests/web-apps/mastodon/remote-postgresql.nix160
-rw-r--r--nixos/tests/web-apps/mastodon/script.nix54
-rw-r--r--nixos/tests/web-apps/mastodon/standard.nix92
-rw-r--r--nixos/tests/web-apps/peering-manager.nix40
-rw-r--r--nixos/tests/web-apps/peertube.nix7
-rw-r--r--nixos/tests/web-apps/phylactery.nix20
-rw-r--r--nixos/tests/web-apps/writefreely.nix44
-rw-r--r--nixos/tests/web-servers/agate.nix46
-rw-r--r--nixos/tests/web-servers/unit-php.nix17
-rw-r--r--nixos/tests/wine.nix5
-rw-r--r--nixos/tests/wireguard/wg-quick.nix5
-rw-r--r--nixos/tests/wrappers.nix79
-rw-r--r--nixos/tests/xmonad.nix15
-rw-r--r--nixos/tests/xmpp/prosody.nix3
-rw-r--r--nixos/tests/xmpp/xmpp-sendmessage.nix15
-rw-r--r--nixos/tests/xpadneo.nix18
-rw-r--r--nixos/tests/xrdp.nix2
-rw-r--r--nixos/tests/yggdrasil.nix4
-rw-r--r--nixos/tests/zeronet-conservancy.nix25
-rw-r--r--nixos/tests/zfs.nix4
-rw-r--r--nixos/tests/zigbee2mqtt.nix2
-rw-r--r--nixos/tests/zrepl.nix6
-rw-r--r--nixos/tests/zsh-history.nix6
2068 files changed, 61309 insertions, 30596 deletions
diff --git a/nixos/doc/manual/administration/declarative-containers.section.md b/nixos/doc/manual/administration/declarative-containers.section.md
index 0d9d4017ed81..eaa50d3c663d 100644
--- a/nixos/doc/manual/administration/declarative-containers.section.md
+++ b/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;
       };
   };
 ```
@@ -40,7 +40,7 @@ section for details on container networking.)
 To disable the container, just remove it from `configuration.nix` and
 run `nixos-rebuild
   switch`. Note that this will not delete the root directory of the
-container in `/var/lib/containers`. Containers can be destroyed using
+container in `/var/lib/nixos-containers`. Containers can be destroyed using
 the imperative method: `nixos-container destroy foo`.
 
 Declarative containers can be started and stopped using the
diff --git a/nixos/doc/manual/administration/imperative-containers.section.md b/nixos/doc/manual/administration/imperative-containers.section.md
index 05196bf5d819..f45991780c4b 100644
--- a/nixos/doc/manual/administration/imperative-containers.section.md
+++ b/nixos/doc/manual/administration/imperative-containers.section.md
@@ -10,8 +10,8 @@ You create a container with identifier `foo` as follows:
 # nixos-container create foo
 ```
 
-This creates the container's root directory in `/var/lib/containers/foo`
-and a small configuration file in `/etc/containers/foo.conf`. It also
+This creates the container's root directory in `/var/lib/nixos-containers/foo`
+and a small configuration file in `/etc/nixos-containers/foo.conf`. It also
 builds the container's initial system configuration and stores it in
 `/nix/var/nix/profiles/per-container/foo/system`. You can modify the
 initial configuration of the container on the command line. For
diff --git a/nixos/doc/manual/configuration/adding-custom-packages.section.md b/nixos/doc/manual/configuration/adding-custom-packages.section.md
index 5d1198fb0f41..9219396722f0 100644
--- a/nixos/doc/manual/configuration/adding-custom-packages.section.md
+++ b/nixos/doc/manual/configuration/adding-custom-packages.section.md
@@ -1,11 +1,18 @@
 # Adding Custom Packages {#sec-custom-packages}
 
 It's possible that a package you need is not available in NixOS. In that
-case, you can do two things. First, 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 [Nixpkgs manual](https://nixos.org/nixpkgs/manual).
-In short, you clone Nixpkgs:
+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.
+
+## Building with Nix {#sec-custom-packages-nix}
+
+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 [Nixpkgs
+manual](https://nixos.org/nixpkgs/manual). In short, you clone Nixpkgs:
 
 ```ShellSession
 $ git clone https://github.com/NixOS/nixpkgs
@@ -72,3 +79,21 @@ $ nix-build my-hello.nix
 $ ./result/bin/hello
 Hello, world!
 ```
+
+## Using pre-built executables {#sec-custom-packages-prebuilt}
+
+Most pre-built executables will not work on NixOS. There are two notable
+exceptions: flatpaks and AppImages. For flatpaks see the [dedicated
+section](#module-services-flatpak). AppImages will not run "as-is" on NixOS.
+First you need to install `appimage-run`: add to `/etc/nixos/configuration.nix`
+
+```nix
+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
+the [Nixpkgs manual](https://nixos.org/nixpkgs/manual) for details. This
+is complex and often doing a source build is easier.
diff --git a/nixos/doc/manual/configuration/config-file.section.md b/nixos/doc/manual/configuration/config-file.section.md
index f21ba113bf8c..efd231fd1f4e 100644
--- a/nixos/doc/manual/configuration/config-file.section.md
+++ b/nixos/doc/manual/configuration/config-file.section.md
@@ -166,7 +166,7 @@ Packages
         pkgs.emacs
       ];
 
-    services.postgresql.package = pkgs.postgresql_10;
+    services.postgresql.package = pkgs.postgresql_14;
     ```
 
     The latter option definition changes the default PostgreSQL package
diff --git a/nixos/doc/manual/configuration/config-syntax.chapter.md b/nixos/doc/manual/configuration/config-syntax.chapter.md
index 56d093c0f6e8..9f8d45d58899 100644
--- a/nixos/doc/manual/configuration/config-syntax.chapter.md
+++ b/nixos/doc/manual/configuration/config-syntax.chapter.md
@@ -15,5 +15,4 @@ NixOS configuration files.
 <xi:include href="config-file.section.xml" />
 <xi:include href="abstractions.section.xml" />
 <xi:include href="modularity.section.xml" />
-<xi:include href="summary.section.xml" />
 ```
diff --git a/nixos/doc/manual/configuration/gpu-accel.chapter.md b/nixos/doc/manual/configuration/gpu-accel.chapter.md
index 08b6af5d98ae..aa41e25e56f3 100644
--- a/nixos/doc/manual/configuration/gpu-accel.chapter.md
+++ b/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}
@@ -169,7 +203,7 @@ configuration, GPU devices have world-read/write permissions
 (`/dev/dri/renderD*`) or are tagged as `uaccess` (`/dev/dri/card*`). The
 access control lists of devices with the `uaccess` tag will be updated
 automatically when a user logs in through `systemd-logind`. For example,
-if the user *jane* is logged in, the access control list should look as
+if the user *alice* is logged in, the access control list should look as
 follows:
 
 ```ShellSession
@@ -178,7 +212,7 @@ $ getfacl /dev/dri/card0
 # owner: root
 # group: video
 user::rw-
-user:jane:rw-
+user:alice:rw-
 group::rw-
 mask::rw-
 other::---
diff --git a/nixos/doc/manual/configuration/kubernetes.chapter.md b/nixos/doc/manual/configuration/kubernetes.chapter.md
index 93787577be9b..5d7b083289d9 100644
--- a/nixos/doc/manual/configuration/kubernetes.chapter.md
+++ b/nixos/doc/manual/configuration/kubernetes.chapter.md
@@ -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/nixos/doc/manual/configuration/linux-kernel.chapter.md b/nixos/doc/manual/configuration/linux-kernel.chapter.md
index 1d06543d4f1e..7b84416a8646 100644
--- a/nixos/doc/manual/configuration/linux-kernel.chapter.md
+++ b/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:
@@ -138,3 +148,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/nixos/doc/manual/configuration/profiles/hardened.section.md b/nixos/doc/manual/configuration/profiles/hardened.section.md
index 9fb5e18c384a..2e9bb196c054 100644
--- a/nixos/doc/manual/configuration/profiles/hardened.section.md
+++ b/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/nixos/doc/manual/configuration/summary.section.md b/nixos/doc/manual/configuration/summary.section.md
deleted file mode 100644
index 8abbbe257fd9..000000000000
--- a/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/nixos/doc/manual/configuration/user-mgmt.chapter.md b/nixos/doc/manual/configuration/user-mgmt.chapter.md
index 37990664a8f1..5c3aca3ef9e9 100644
--- a/nixos/doc/manual/configuration/user-mgmt.chapter.md
+++ b/nixos/doc/manual/configuration/user-mgmt.chapter.md
@@ -32,8 +32,7 @@ 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
 [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/nixos/doc/manual/configuration/wireless.section.md b/nixos/doc/manual/configuration/wireless.section.md
index 6b223d843ac5..3299d2d7ecb8 100644
--- a/nixos/doc/manual/configuration/wireless.section.md
+++ b/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/nixos/doc/manual/configuration/x-windows.chapter.md b/nixos/doc/manual/configuration/x-windows.chapter.md
index 2c80b786b267..27d117238807 100644
--- a/nixos/doc/manual/configuration/x-windows.chapter.md
+++ b/nixos/doc/manual/configuration/x-windows.chapter.md
@@ -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
diff --git a/nixos/doc/manual/configuration/xfce.chapter.md b/nixos/doc/manual/configuration/xfce.chapter.md
index b0ef6682aae8..ee60d465e3b3 100644
--- a/nixos/doc/manual/configuration/xfce.chapter.md
+++ b/nixos/doc/manual/configuration/xfce.chapter.md
@@ -24,11 +24,16 @@ Some Xfce programs are not installed automatically. To install them
 manually (system wide), put them into your
 [](#opt-environment.systemPackages) from `pkgs.xfce`.
 
-## Thunar Plugins {#sec-xfce-thunar-plugins .unnumbered}
+## Thunar {#sec-xfce-thunar-plugins .unnumbered}
+
+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-services.xserver.desktopManager.xfce.thunarPlugins).
-You shouldn\'t just add them to [](#opt-environment.systemPackages).
+[](#opt-programs.thunar.plugins). You shouldn\'t just add them to
+[](#opt-environment.systemPackages).
 
 ## Troubleshooting {#sec-xfce-troubleshooting .unnumbered}
 
diff --git a/nixos/doc/manual/contributing-to-this-manual.chapter.md b/nixos/doc/manual/contributing-to-this-manual.chapter.md
index 26813d1042d6..557599809222 100644
--- a/nixos/doc/manual/contributing-to-this-manual.chapter.md
+++ b/nixos/doc/manual/contributing-to-this-manual.chapter.md
@@ -1,6 +1,6 @@
 # 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:
 
@@ -11,3 +11,25 @@ $ 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/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index bcb5d0d02f74..9b72e840f4b1 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -6,12 +6,15 @@
 , 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 {
@@ -28,13 +31,41 @@ 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;
     };
   };
 
+  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-";
+    };
+
   sources = lib.sourceFilesBySuffices ./. [".xml"];
 
   modulesDoc = builtins.toFile "modules.xml" ''
@@ -49,6 +80,7 @@ let
     mkdir $out
     ln -s ${modulesDoc} $out/modules.xml
     ln -s ${optionsDoc.optionsDocBook} $out/options-db.xml
+    ln -s ${testOptionsDoc.optionsDocBook} $out/test-options-db.xml
     printf "%s" "${version}" > $out/version
   '';
 
@@ -70,11 +102,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 ''"
@@ -133,12 +168,12 @@ let
           # ^ redirect assumes xmllint doesn’t print to stdout
       }
 
-      lintrng manual-combined.xml
-      lintrng man-pages-combined.xml
-
       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"
diff --git a/nixos/doc/manual/development/activation-script.section.md b/nixos/doc/manual/development/activation-script.section.md
index df6836624040..c339258c6dc4 100644
--- a/nixos/doc/manual/development/activation-script.section.md
+++ b/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,12 +49,12 @@ 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}
 
 There are some snippets NixOS enables by default because disabling them would
-most likely break you system. This section lists a few of them and what they
+most likely break your system. This section lists a few of them and what they
 do:
 
 - `binsh` creates `/bin/sh` which points to the runtime shell
diff --git a/nixos/doc/manual/development/bootspec.chapter.md b/nixos/doc/manual/development/bootspec.chapter.md
new file mode 100644
index 000000000000..96c12f24e7f1
--- /dev/null
+++ b/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/nixos/doc/manual/development/development.xml b/nixos/doc/manual/development/development.xml
index 624ee3931659..949468c9021d 100644
--- a/nixos/doc/manual/development/development.xml
+++ b/nixos/doc/manual/development/development.xml
@@ -12,6 +12,7 @@
  <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/bootspec.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" />
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index 2e11218e8238..88617ab1920a 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/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,19 +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
-    included in the NixOS manual.
+:   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
+    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,7 +84,7 @@ lib.mkOption {
   type = lib.types.bool;
   default = false;
   example = true;
-  description = "Whether to enable magic.";
+  description = lib.mdDoc "Whether to enable magic.";
 }
 ```
 
@@ -112,7 +117,7 @@ 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.";
 }
 ```
 
@@ -120,15 +125,15 @@ lib.mkOption {
 ```nix
 lib.mkPackageOption pkgs "GHC" {
   default = [ "ghc" ];
-  example = "pkgs.haskell.package.ghc922.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.package.ghc922.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.";
 }
 ```
 
diff --git a/nixos/doc/manual/development/option-def.section.md b/nixos/doc/manual/development/option-def.section.md
index 91b24cd4a3a1..22cf38873cf0 100644
--- a/nixos/doc/manual/development/option-def.section.md
+++ b/nixos/doc/manual/development/option-def.section.md
@@ -59,17 +59,35 @@ config = {
 ## Setting Priorities {#sec-option-definitions-setting-priorities .unnumbered}
 
 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`.
+
+## Ordering Definitions {#sec-option-definitions-ordering .unnumbered}
+
+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 .unnumbered}
 
diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md
index 00f1d85bdb61..e398d6c30cce 100644
--- a/nixos/doc/manual/development/option-types.section.md
+++ b/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,6 +25,11 @@ 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
@@ -95,7 +100,7 @@ merging is handled.
     problems.
     :::
 
-Integer-related types:
+### Numeric types {#sec-option-types-numeric}
 
 `types.int`
 
@@ -118,6 +123,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 +136,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 +185,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 +193,9 @@ String-related types:
     definitions cannot be merged. The regular expression is processed
     using `builtins.match`.
 
-## Value Types {#sec-option-types-value}
-
-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`*
+## Submodule types {#sec-option-types-submodule}
 
-:   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 +204,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 }
 
@@ -220,7 +245,26 @@ Value types are types that take a value parameter.
         requires using a function:
         `the-submodule = { ... }: { options = { ... }; }`.
 
-## Composed Types {#sec-option-types-composed}
+`types.deferredModule`
+
+:   Whereas `submodule` represents an option tree, `deferredModule` represents
+    a module value, such as a module file or a configuration.
+
+    It can be set multiple times.
+
+    Module authors can use its value in `imports`, in `submoduleWith`'s `modules`
+    or in `evalModules`' `modules` parameter, among other places.
+
+    Note that `imports` must be evaluated before the module fixpoint. Because
+    of this, deferred modules can only be imported into "other" fixpoints, such
+    as submodules.
+
+    One use case for this type is the type of a "default" module that allow the
+    user to affect all submodules in an `attrsOf submodule` at once. This is
+    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 are types that take a type as parameter. `listOf
    int` and `either int str` are examples of composed types.
@@ -301,13 +345,17 @@ 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
 ([Example: Directly defined submodule](#ex-submodule-direct)) or as reference
 ([Example: Submodule defined as a reference](#ex-submodule-reference)).
 
+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.
+
 ::: {#ex-submodule-direct .example}
 ::: {.title}
 **Example: Directly defined submodule**
@@ -473,7 +521,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/nixos/doc/manual/development/running-nixos-tests-interactively.section.md b/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
index a1431859ff59..1130672cb376 100644
--- a/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
+++ b/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
@@ -24,6 +24,8 @@ 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).
 
+## 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 +35,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/nixos/doc/manual/development/running-nixos-tests.section.md b/nixos/doc/manual/development/running-nixos-tests.section.md
index 1bec023b613a..33076f5dc2a7 100644
--- a/nixos/doc/manual/development/running-nixos-tests.section.md
+++ b/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/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md
index e5ee1cb01ff1..f3edea3e7047 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/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`
 
@@ -121,7 +171,7 @@ The following methods are available on machine objects:
     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`
@@ -130,7 +180,7 @@ The following methods are available on machine objects:
     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`
@@ -241,14 +291,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`
 
@@ -301,10 +351,10 @@ 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):
+(this shouldn\'t be committed though):
 
 ```nix
-import ./make-test-python.nix {
+{
   skipLint = true;
   nodes.machine =
     { config, pkgs, ... }:
@@ -332,9 +382,22 @@ repository):
     '';
 ```
 
+Similarly, the type checking of test scripts can be disabled in the following
+way:
+
+```nix
+{
+  skipTypeCheck = true;
+  nodes.machine =
+    { config, pkgs, ... }:
+    { configuration…
+    };
+}
+```
+
 ## Failing tests early {#ssec-failing-tests-early}
 
-To fail tests early when certain invariables are no longer met (instead of waiting for the build to time out), the decorator `polling_condition` is provided. For example, if we are testing a program `foo` that should not quit after being started, we might write the following:
+To fail tests early when certain invariants are no longer met (instead of waiting for the build to time out), the decorator `polling_condition` is provided. For example, if we are testing a program `foo` that should not quit after being started, we might write the following:
 
 ```py
 @polling_condition
@@ -349,7 +412,6 @@ with foo_running:
     ...  # Put `foo` through its paces
 ```
 
-
 `polling_condition` takes the following (optional) arguments:
 
 `seconds_interval`
@@ -357,26 +419,58 @@ with foo_running:
 :
     specifies how often the condition should be polled:
 
-    ```py
-    @polling_condition(seconds_interval=10)
-    def foo_running():
-        machine.succeed("pgrep -x foo")
-    ```
+```py
+@polling_condition(seconds_interval=10)
+def foo_running():
+    machine.succeed("pgrep -x foo")
+```
 
 `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:
 
-    ```py
-    @polling_condition
-    def foo_running():
-        "check that foo is running"
-        machine.succeed("pgrep -x foo")
-    ```
+```py
+@polling_condition
+def foo_running():
+    "check that foo is running"
+    machine.succeed("pgrep -x foo")
+```
 
-    ```py
-    @polling_condition(description="check that foo is running")
-    def foo_running():
-        machine.succeed("pgrep -x foo")
-    ```
+```py
+@polling_condition(description="check that foo is running")
+def foo_running():
+    machine.succeed("pgrep -x foo")
+```
+
+## Adding Python packages to the test script {#ssec-python-packages-in-test-script}
+
+When additional Python libraries are required in the test script, they can be
+added using the parameter `extraPythonPackages`. For example, you could add
+`numpy` like this:
+
+```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) == "array([0., 0., 0., 0.])")
+  '';
+}
+```
+
+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.
+
+```{=docbook}
+<xi:include href="../../generated/test-options-db.xml" xpointer="test-options-list"/>
+```
diff --git a/nixos/doc/manual/from_md/administration/declarative-containers.section.xml b/nixos/doc/manual/from_md/administration/declarative-containers.section.xml
index 7b35520d567b..4831c9c74e84 100644
--- a/nixos/doc/manual/from_md/administration/declarative-containers.section.xml
+++ b/nixos/doc/manual/from_md/administration/declarative-containers.section.xml
@@ -11,7 +11,7 @@ containers.database =
   { config =
       { config, pkgs, ... }:
       { services.postgresql.enable = true;
-      services.postgresql.package = pkgs.postgresql_10;
+      services.postgresql.package = pkgs.postgresql_14;
       };
   };
 </programlisting>
@@ -48,8 +48,8 @@ containers.database = {
     <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/containers</literal>. Containers can be destroyed
-    using the imperative method:
+    <literal>/var/lib/nixos-containers</literal>. Containers can be
+    destroyed using the imperative method:
     <literal>nixos-container destroy foo</literal>.
   </para>
   <para>
diff --git a/nixos/doc/manual/from_md/administration/imperative-containers.section.xml b/nixos/doc/manual/from_md/administration/imperative-containers.section.xml
index 59ecfdee5af0..865fc4689398 100644
--- a/nixos/doc/manual/from_md/administration/imperative-containers.section.xml
+++ b/nixos/doc/manual/from_md/administration/imperative-containers.section.xml
@@ -14,8 +14,9 @@
 </programlisting>
   <para>
     This creates the container’s root directory in
-    <literal>/var/lib/containers/foo</literal> and a small configuration
-    file in <literal>/etc/containers/foo.conf</literal>. It also builds
+    <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
diff --git a/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml b/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
index 4fa40d61966e..07f541666cbe 100644
--- a/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
+++ b/nixos/doc/manual/from_md/configuration/adding-custom-packages.section.xml
@@ -2,40 +2,50 @@
   <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. First, 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:
+    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>
-  <programlisting>
+  <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">
+    <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>
+    <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">
+    <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 {
@@ -48,17 +58,17 @@ environment.systemPackages =
   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">
+    <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">
+    <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 {
@@ -69,12 +79,40 @@ stdenv.mkDerivation rec {
   };
 }
 </programlisting>
-  <para>
-    This allows testing the package easily:
-  </para>
-  <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/nixos/doc/manual/from_md/configuration/config-file.section.xml b/nixos/doc/manual/from_md/configuration/config-file.section.xml
index 952c6e600302..9792116eb08d 100644
--- a/nixos/doc/manual/from_md/configuration/config-file.section.xml
+++ b/nixos/doc/manual/from_md/configuration/config-file.section.xml
@@ -217,7 +217,7 @@ environment.systemPackages =
     pkgs.emacs
   ];
 
-services.postgresql.package = pkgs.postgresql_10;
+services.postgresql.package = pkgs.postgresql_14;
 </programlisting>
         <para>
           The latter option definition changes the default PostgreSQL
diff --git a/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml b/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml
index 01446e53e38f..baf9639554cc 100644
--- a/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/config-syntax.chapter.xml
@@ -17,5 +17,4 @@
   <xi:include href="config-file.section.xml" />
   <xi:include href="abstractions.section.xml" />
   <xi:include href="modularity.section.xml" />
-  <xi:include href="summary.section.xml" />
 </chapter>
diff --git a/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml b/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml
index 8e780c5dee95..90d2c17e12ef 100644
--- a/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/gpu-accel.chapter.xml
@@ -180,6 +180,48 @@ environment.variables.VK_ICD_FILENAMES =
 </programlisting>
     </section>
   </section>
+  <section xml:id="sec-gpu-accel-va-api">
+    <title>VA-API</title>
+    <para>
+      <link xlink:href="https://www.intel.com/content/www/us/en/developer/articles/technical/linuxmedia-vaapi.html">VA-API
+      (Video Acceleration API)</link> is an open-source library and API
+      specification, which provides access to graphics hardware
+      acceleration capabilities for video processing.
+    </para>
+    <para>
+      VA-API drivers are loaded by <literal>libva</literal>. The version
+      in nixpkgs is built to search the opengl driver path, so drivers
+      can be installed in
+      <xref linkend="opt-hardware.opengl.extraPackages" />.
+    </para>
+    <para>
+      VA-API can be tested using:
+    </para>
+    <programlisting>
+$ nix-shell -p libva-utils --run vainfo
+</programlisting>
+    <section xml:id="sec-gpu-accel-va-api-intel">
+      <title>Intel</title>
+      <para>
+        Modern Intel GPUs use the iHD driver, which can be installed
+        with:
+      </para>
+      <programlisting language="bash">
+hardware.opengl.extraPackages = [
+  intel-media-driver
+];
+</programlisting>
+      <para>
+        Older Intel GPUs use the i965 driver, which can be installed
+        with:
+      </para>
+      <programlisting language="bash">
+hardware.opengl.extraPackages = [
+  vaapiIntel
+];
+</programlisting>
+    </section>
+  </section>
   <section xml:id="sec-gpu-accel-common-issues">
     <title>Common issues</title>
     <section xml:id="sec-gpu-accel-common-issues-permissions">
@@ -194,7 +236,7 @@ environment.variables.VK_ICD_FILENAMES =
         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>jane</emphasis> is logged in, the access control list
+        <emphasis>alice</emphasis> is logged in, the access control list
         should look as follows:
       </para>
       <programlisting>
@@ -203,7 +245,7 @@ $ getfacl /dev/dri/card0
 # owner: root
 # group: video
 user::rw-
-user:jane:rw-
+user:alice:rw-
 group::rw-
 mask::rw-
 other::---
diff --git a/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml b/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml
index 83a50d7c49d1..1de19f64bdad 100644
--- a/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/kubernetes.chapter.xml
@@ -47,17 +47,6 @@ services.kubernetes.roles = [ &quot;master&quot; &quot;node&quot; ];
     <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:
diff --git a/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml b/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
index a1d6815af29c..dd570e1d66c2 100644
--- a/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/linux-kernel.chapter.xml
@@ -22,6 +22,19 @@ boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_10;
     date with new versions.
   </para>
   <para>
+    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.
+  </para>
+  <para>
+    Longterm versions of kernels will be removed before the next stable
+    NixOS that will exceed the maintenance period of the kernel version.
+  </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:
@@ -154,4 +167,38 @@ $ make -C $dev/lib/modules/*/build M=$(pwd)/drivers/net/ethernet/mellanox module
 # insmod ./drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.ko
 </programlisting>
   </section>
+  <section xml:id="sec-linux-zfs">
+    <title>ZFS</title>
+    <para>
+      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.
+      <literal>pkgs.linuxPackages</literal>).
+    </para>
+    <para>
+      Alternatively, it’s possible to pin the system to the latest
+      available kernel version <emphasis>that is supported by
+      ZFS</emphasis> like this:
+    </para>
+    <programlisting language="bash">
+{
+  boot.kernelPackages = pkgs.zfs.latestCompatibleLinuxPackages;
+}
+</programlisting>
+    <para>
+      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.
+    </para>
+    <para>
+      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.
+    </para>
+  </section>
 </chapter>
diff --git a/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml b/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml
index 44c11786d940..1fd5a9179887 100644
--- a/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml
+++ b/nixos/doc/manual/from_md/configuration/profiles/hardened.section.xml
@@ -9,7 +9,7 @@
     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
+    build anything (this particular setting can be overridden 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.
diff --git a/nixos/doc/manual/from_md/configuration/summary.section.xml b/nixos/doc/manual/from_md/configuration/summary.section.xml
deleted file mode 100644
index 96a178c4930e..000000000000
--- a/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/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml b/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml
index 06492d5c2512..a2d7d2a9f115 100644
--- a/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/user-mgmt.chapter.xml
@@ -39,7 +39,7 @@ users.users.alice = {
     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>.
+    <literal>mkpasswd</literal>.
   </para>
   <para>
     A user ID (uid) is assigned automatically. You can also specify a
diff --git a/nixos/doc/manual/from_md/configuration/wireless.section.xml b/nixos/doc/manual/from_md/configuration/wireless.section.xml
index 82bc20135157..d39ec4fac493 100644
--- a/nixos/doc/manual/from_md/configuration/wireless.section.xml
+++ b/nixos/doc/manual/from_md/configuration/wireless.section.xml
@@ -54,7 +54,7 @@ networking.wireless.networks = {
   echelon = {
     pskRaw = &quot;dca6d6ed41f4ab5a984c9f55f6f66d4efdc720ebf66959810f4329bb391c5435&quot;;
   };
-}
+};
 </programlisting>
   <para>
     or you can use it to directly generate the
diff --git a/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml b/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml
index 274d0d817bc1..c17e98983b27 100644
--- a/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/x-windows.chapter.xml
@@ -133,7 +133,6 @@ services.xserver.displayManager.autoLogin.user = &quot;alice&quot;;
     </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
diff --git a/nixos/doc/manual/from_md/configuration/xfce.chapter.xml b/nixos/doc/manual/from_md/configuration/xfce.chapter.xml
index f96ef2e8c483..42e70d1d81d3 100644
--- a/nixos/doc/manual/from_md/configuration/xfce.chapter.xml
+++ b/nixos/doc/manual/from_md/configuration/xfce.chapter.xml
@@ -27,13 +27,19 @@ services.picom = {
     <literal>pkgs.xfce</literal>.
   </para>
   <section xml:id="sec-xfce-thunar-plugins">
-    <title>Thunar Plugins</title>
+    <title>Thunar</title>
     <para>
-      If you'd like to add extra plugins to Thunar, add them to
-      <xref linkend="opt-services.xserver.desktopManager.xfce.thunarPlugins" />.
-      You shouldn't just add them to
+      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>
diff --git a/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml b/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml
index a9b0c6a5eefa..99dc5ce30b4b 100644
--- a/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml
+++ b/nixos/doc/manual/from_md/contributing-to-this-manual.chapter.xml
@@ -1,7 +1,9 @@
 <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
+    The
+    <link xlink:href="https://en.wikipedia.org/wiki/DocBook">DocBook</link>
+    and CommonMark sources of the 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>
@@ -19,4 +21,32 @@ $ nix-build nixos/release.nix -A manual.x86_64-linux
     If the build succeeds, the manual will be in
     <literal>./result/share/doc/nixos/index.html</literal>.
   </para>
+  <para>
+    <emphasis role="strong">Contributing to the man pages</emphasis>
+  </para>
+  <para>
+    The man pages are written in
+    <link xlink:href="https://en.wikipedia.org/wiki/DocBook">DocBook</link>
+    which is XML.
+  </para>
+  <para>
+    To see what your edits look like:
+  </para>
+  <programlisting>
+$ cd /path/to/nixpkgs
+$ nix-build nixos/release.nix -A manpages.x86_64-linux
+</programlisting>
+  <para>
+    You can then read the man page you edited by running
+  </para>
+  <programlisting>
+$ man --manpath=result/share/man nixos-rebuild # Replace nixos-rebuild with the command whose manual you edited
+</programlisting>
+  <para>
+    If you’re on a different architecture that’s supported by NixOS
+    (check nixos/release.nix) then replace
+    <literal>x86_64-linux</literal> with the architecture.
+    <literal>nix-build</literal> will complain otherwise, but should
+    also tell you which architecture you have + the supported ones.
+  </para>
 </chapter>
diff --git a/nixos/doc/manual/from_md/development/activation-script.section.xml b/nixos/doc/manual/from_md/development/activation-script.section.xml
index 0d9e911216ef..8672ab8afe54 100644
--- a/nixos/doc/manual/from_md/development/activation-script.section.xml
+++ b/nixos/doc/manual/from_md/development/activation-script.section.xml
@@ -45,7 +45,7 @@ system.activationScripts.my-activation-script = {
     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
+    incorporate 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
@@ -66,14 +66,14 @@ system.activationScripts.my-activation-script = {
     <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
+    automatically and activation scripts must take the possibility 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 you system. This section lists a few
+      them would most likely break your system. This section lists a few
       of them and what they do:
     </para>
     <itemizedlist spacing="compact">
diff --git a/nixos/doc/manual/from_md/development/bootspec.chapter.xml b/nixos/doc/manual/from_md/development/bootspec.chapter.xml
new file mode 100644
index 000000000000..acf8ca76bf5c
--- /dev/null
+++ b/nixos/doc/manual/from_md/development/bootspec.chapter.xml
@@ -0,0 +1,73 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-experimental-bootspec">
+  <title>Experimental feature: Bootspec</title>
+  <para>
+    Bootspec is a experimental feature, introduced in the
+    <link xlink:href="https://github.com/NixOS/rfcs/pull/125">RFC-0125
+    proposal</link>, the reference implementation can be found
+    <link xlink:href="https://github.com/NixOS/nixpkgs/pull/172237">there</link>
+    in order to standardize bootloader support and advanced boot
+    workflows such as SecureBoot and potentially more.
+  </para>
+  <para>
+    You can enable the creation of bootspec documents through
+    <link xlink:href="options.html#opt-boot.bootspec.enable"><literal>boot.bootspec.enable = true</literal></link>,
+    which will prompt a warning until
+    <link xlink:href="https://github.com/NixOS/rfcs/pull/125">RFC-0125</link>
+    is officially merged.
+  </para>
+  <section xml:id="sec-experimental-bootspec-schema">
+    <title>Schema</title>
+    <para>
+      The bootspec schema is versioned and validated against
+      <link xlink:href="https://cuelang.org/">a CUE schema file</link>
+      which should considered as the source of truth for your
+      applications.
+    </para>
+    <para>
+      You will find the current version
+      <link xlink:href="../../../modules/system/activation/bootspec.cue">here</link>.
+    </para>
+  </section>
+  <section xml:id="sec-experimental-bootspec-extensions">
+    <title>Extensions mechanism</title>
+    <para>
+      Bootspec cannot account for all usecases.
+    </para>
+    <para>
+      For this purpose, Bootspec offers a generic extension facility
+      <link xlink:href="options.html#opt-boot.bootspec.extensions"><literal>boot.bootspec.extensions</literal></link>
+      which can be used to inject any data needed for your usecases.
+    </para>
+    <para>
+      An example for SecureBoot is to get the Nix store path to
+      <literal>/etc/os-release</literal> in order to bake it into a
+      unified kernel image:
+    </para>
+    <programlisting language="bash">
+{ config, lib, ... }: {
+  boot.bootspec.extensions = {
+    &quot;org.secureboot.osRelease&quot; = config.environment.etc.&quot;os-release&quot;.source;
+  };
+}
+</programlisting>
+    <para>
+      To reduce incompatibility and prevent names from clashing between
+      applications, it is <emphasis role="strong">highly
+      recommended</emphasis> to use a unique namespace for your
+      extensions.
+    </para>
+  </section>
+  <section xml:id="sec-experimental-bootspec-external-bootloaders">
+    <title>External bootloaders</title>
+    <para>
+      It is possible to enable your own bootloader through
+      <link xlink:href="options.html#opt-boot.loader.external.installHook"><literal>boot.loader.external.installHook</literal></link>
+      which can wrap an existing bootloader.
+    </para>
+    <para>
+      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.
+    </para>
+  </section>
+</chapter>
diff --git a/nixos/doc/manual/from_md/development/option-declarations.section.xml b/nixos/doc/manual/from_md/development/option-declarations.section.xml
index 91867c224107..0932a51a18cd 100644
--- a/nixos/doc/manual/from_md/development/option-declarations.section.xml
+++ b/nixos/doc/manual/from_md/development/option-declarations.section.xml
@@ -12,7 +12,7 @@ options = {
     type = type specification;
     default = default value;
     example = example value;
-    description = &quot;Description for use in the NixOS manual.&quot;;
+    description = lib.mdDoc &quot;Description for use in the NixOS manual.&quot;;
   };
 };
 </programlisting>
@@ -69,8 +69,10 @@ options = {
           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.
+          <literal>lib.literalMD</literal> for a plain English
+          description in
+          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-contributing-markup">Nixpkgs-flavored
+          Markdown</link> format.
         </para>
       </listitem>
     </varlistentry>
@@ -82,7 +84,7 @@ options = {
         <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>lib.literalMD</literal> in the same way as in
           <literal>defaultText</literal>.
         </para>
       </listitem>
@@ -93,8 +95,14 @@ options = {
       </term>
       <listitem>
         <para>
-          A textual description of the option, in DocBook format, that
-          will be included in the NixOS manual.
+          A textual description of the option, in
+          <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-contributing-markup">Nixpkgs-flavored
+          Markdown</link> format, that will be included in the NixOS
+          manual. During the migration process from DocBook it is
+          necessary to mark descriptions written in CommonMark with
+          <literal>lib.mdDoc</literal>. The description may still be
+          written in DocBook (without any marker), but this is
+          discouraged and will be deprecated in the future.
         </para>
       </listitem>
     </varlistentry>
@@ -126,7 +134,7 @@ lib.mkOption {
   type = lib.types.bool;
   default = false;
   example = true;
-  description = &quot;Whether to enable magic.&quot;;
+  description = lib.mdDoc &quot;Whether to enable magic.&quot;;
 }
 </programlisting>
       <section xml:id="sec-option-declarations-util-mkPackageOption">
@@ -176,22 +184,22 @@ lib.mkOption {
   type = lib.types.package;
   default = pkgs.hello;
   defaultText = lib.literalExpression &quot;pkgs.hello&quot;;
-  description = &quot;The hello package to use.&quot;;
+  description = lib.mdDoc &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.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
+  example = &quot;pkgs.haskell.packages.ghc92.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.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
-  description = &quot;The GHC package to use.&quot;;
+  example = lib.literalExpression &quot;pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
+  description = lib.mdDoc &quot;The GHC package to use.&quot;;
 }
 </programlisting>
         <section xml:id="sec-option-declarations-eot">
diff --git a/nixos/doc/manual/from_md/development/option-def.section.xml b/nixos/doc/manual/from_md/development/option-def.section.xml
index 8c9ef181affd..3c1a979e70f3 100644
--- a/nixos/doc/manual/from_md/development/option-def.section.xml
+++ b/nixos/doc/manual/from_md/development/option-def.section.xml
@@ -66,11 +66,11 @@ config = {
     <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.
+      modules by setting an <emphasis>override priority</emphasis>. All
+      option definitions that do not have the lowest priority value are
+      discarded. By default, option definitions have priority 100 and
+      option defaults have priority 1500. You can specify an explicit
+      priority by using <literal>mkOverride</literal>, e.g.
     </para>
     <programlisting language="bash">
 services.openssh.enable = mkOverride 10 false;
@@ -78,7 +78,35 @@ services.openssh.enable = mkOverride 10 false;
     <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>.
+      equal to <literal>mkOverride 50</literal>, and
+      <literal>mkDefault</literal> is equal to
+      <literal>mkOverride 1000</literal>.
+    </para>
+  </section>
+  <section xml:id="sec-option-definitions-ordering">
+    <title>Ordering Definitions</title>
+    <para>
+      It is also possible to influence the order in which the
+      definitions for an option are merged by setting an <emphasis>order
+      priority</emphasis> with <literal>mkOrder</literal>. The default
+      order priority is 1000. The functions <literal>mkBefore</literal>
+      and <literal>mkAfter</literal> are equal to
+      <literal>mkOrder 500</literal> and
+      <literal>mkOrder 1500</literal>, respectively. As an example,
+    </para>
+    <programlisting language="bash">
+hardware.firmware = mkBefore [ myFirmware ];
+</programlisting>
+    <para>
+      This definition ensures that <literal>myFirmware</literal> comes
+      before other unordered definitions in the final list value of
+      <literal>hardware.firmware</literal>.
+    </para>
+    <para>
+      Note that this is different from
+      <link linkend="sec-option-definitions-setting-priorities">override
+      priorities</link>: setting an order does not affect whether the
+      definition is included or not.
     </para>
   </section>
   <section xml:id="sec-option-definitions-merging">
diff --git a/nixos/doc/manual/from_md/development/option-types.section.xml b/nixos/doc/manual/from_md/development/option-types.section.xml
index 444729292702..c0f40cb34232 100644
--- a/nixos/doc/manual/from_md/development/option-types.section.xml
+++ b/nixos/doc/manual/from_md/development/option-types.section.xml
@@ -6,7 +6,7 @@
     in case of multiple value definitions.
   </para>
   <section xml:id="sec-option-types-basic">
-    <title>Basic Types</title>
+    <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
@@ -51,6 +51,20 @@
       </varlistentry>
       <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.anything</literal>
         </term>
         <listitem>
@@ -150,188 +164,243 @@
         </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 xml:id="sec-option-types-numeric">
+      <title>Numeric types</title>
+      <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.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).
+            </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>
+        <varlistentry>
+          <term>
+            <literal>types.float</literal>
+          </term>
+          <listitem>
+            <para>
+              A floating point number.
+            </para>
+            <warning>
+              <para>
+                Converting a floating point number to a string with
+                <literal>toString</literal> or <literal>toJSON</literal>
+                may result in
+                <link xlink:href="https://github.com/NixOS/nix/issues/5733">precision
+                loss</link>.
+              </para>
+            </warning>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>
+            <literal>types.number</literal>
+          </term>
+          <listitem>
+            <para>
+              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.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>
+            <literal>types.numbers.between</literal>
+            <emphasis><literal>lowest highest</literal></emphasis>
+          </term>
+          <listitem>
+            <para>
+              An integer or floating point number between
+              <emphasis><literal>lowest</literal></emphasis> and
+              <emphasis><literal>highest</literal></emphasis> (both
+              inclusive).
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>
+            <literal>types.numbers.nonnegative</literal>
+          </term>
+          <listitem>
+            <para>
+              A nonnegative integer or floating point number (that is
+              &gt;= 0).
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>
+            <literal>types.numbers.positive</literal>
+          </term>
+          <listitem>
+            <para>
+              A positive integer or floating point number (that is &gt;
+              0).
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </section>
+    <section xml:id="sec-option-types-string">
+      <title>String types</title>
+      <variablelist>
+        <varlistentry>
+          <term>
+            <literal>types.str</literal>
+          </term>
+          <listitem>
+            <para>
+              A string. Multiple definitions cannot be merged.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>
+            <literal>types.separatedString</literal>
+            <emphasis><literal>sep</literal></emphasis>
+          </term>
+          <listitem>
+            <para>
+              A string. Multiple definitions are concatenated with
+              <emphasis><literal>sep</literal></emphasis>, e.g.
+              <literal>types.separatedString &quot;|&quot;</literal>.
+            </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
+              colon <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>
-  <section xml:id="sec-option-types-value">
-    <title>Value Types</title>
+  <section xml:id="sec-option-types-submodule">
+    <title>Submodule types</title>
     <para>
-      Value types are types that take a value parameter.
+      Submodules are detailed in
+      <link linkend="section-option-types-submodule">Submodule</link>.
     </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>
@@ -345,8 +414,6 @@
             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>
@@ -427,10 +494,47 @@
           </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>
+    <title>Composed types</title>
     <para>
       Composed types are types that take a type as parameter.
       <literal>listOf int</literal> and
@@ -608,7 +712,7 @@
       <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.
+      submodule option definitions for even higher modularity.
     </para>
     <para>
       The option set can be defined directly
@@ -617,6 +721,12 @@
       (<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
@@ -807,7 +917,7 @@ nixThings = mkOption {
     </variablelist>
   </section>
   <section xml:id="sec-option-types-custom">
-    <title>Custom Types</title>
+    <title>Custom types</title>
     <para>
       Custom types can be created with the
       <literal>mkOptionType</literal> function. As type creation
diff --git a/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml b/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml
index 0e47350a0d24..16db709f8b91 100644
--- a/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml
+++ b/nixos/doc/manual/from_md/development/running-nixos-tests-interactively.section.xml
@@ -25,15 +25,40 @@ $ ./result/bin/nixos-test-driver
     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>
+  <section xml:id="sec-nixos-test-reuse-vm-state">
+    <title>Reuse VM state</title>
+    <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>
+    <para>
+      The machine state is stored in the
+      <literal>$TMPDIR/vm-state-machinename</literal> directory.
+    </para>
+  </section>
+  <section xml:id="sec-nixos-test-interactive-configuration">
+    <title>Interactive-only test configuration</title>
+    <para>
+      The <literal>.driverInteractive</literal> attribute combines the
+      regular test configuration with definitions from the
+      <link linkend="test-opt-interactive"><literal>interactive</literal>
+      submodule</link>. This gives you a more usable, graphical, but
+      slightly different configuration.
+    </para>
+    <para>
+      You can add your own interactive-only test configuration by adding
+      extra configuration to the
+      <link linkend="test-opt-interactive"><literal>interactive</literal>
+      submodule</link>.
+    </para>
+    <para>
+      To interactively run only the regular configuration, build the
+      <literal>&lt;test&gt;.driver</literal> attribute instead, and call
+      it with the flag
+      <literal>result/bin/nixos-test-driver --interactive</literal>.
+    </para>
+  </section>
 </section>
diff --git a/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml
index da2e5076c956..23abb546899f 100644
--- a/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml
+++ b/nixos/doc/manual/from_md/development/running-nixos-tests.section.xml
@@ -4,22 +4,11 @@
     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:
+    you 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
+$ cd /my/git/clone/of/nixpkgs
+$ nix-build -A nixosTests.login
 </programlisting>
   <para>
     After building/downloading all required dependencies, this will
diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
index 7ce3e4cb2906..99bd37808c20 100644
--- a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
+++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
@@ -1,10 +1,10 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-writing-nixos-tests">
+<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-writing-nixos-tests">
   <title>Writing Tests</title>
   <para>
-    A NixOS test is a Nix expression that has the following structure:
+    A NixOS test is a module that has the following structure:
   </para>
   <programlisting language="bash">
-import ./make-test-python.nix {
+{
 
   # One or more machines:
   nodes =
@@ -22,10 +22,18 @@ import ./make-test-python.nix {
 }
 </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>.
+    We refer to the whole test above as a test module, whereas the
+    values in
+    <link linkend="test-opt-nodes"><literal>nodes.&lt;name&gt;</literal></link>
+    are NixOS modules themselves.
+  </para>
+  <para>
+    The option
+    <link linkend="test-opt-testScript"><literal>testScript</literal></link>
+    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 option
+    <link linkend="test-opt-nodes"><literal>nodes</literal></link>.
   </para>
   <para>
     An example of a single-node test is
@@ -38,78 +46,138 @@ import ./make-test-python.nix {
     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">
+  <section xml:id="sec-calling-nixos-tests">
+    <title>Calling a test</title>
+    <para>
+      Tests are invoked differently depending on whether the test is
+      part of NixOS or lives in a different project.
+    </para>
+    <section xml:id="sec-call-nixos-test-in-nixos">
+      <title>Testing within NixOS</title>
+      <para>
+        Tests that are part of NixOS are added to
+        <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/all-tests.nix"><literal>nixos/tests/all-tests.nix</literal></link>.
+      </para>
+      <programlisting language="bash">
+  hostname = runTest ./hostname.nix;
+</programlisting>
+      <para>
+        Overrides can be added by defining an anonymous module in
+        <literal>all-tests.nix</literal>.
+      </para>
+      <programlisting language="bash">
+  hostname = runTest {
+    imports = [ ./hostname.nix ];
+    defaults.networking.firewall.enable = false;
+  };
+</programlisting>
+      <para>
+        You can run a test with attribute name
+        <literal>hostname</literal> in
+        <literal>nixos/tests/all-tests.nix</literal> by invoking:
+      </para>
+      <programlisting>
+cd /my/git/clone/of/nixpkgs
+nix-build -A nixosTests.hostname
+</programlisting>
+    </section>
+    <section xml:id="sec-call-nixos-test-outside-nixos">
+      <title>Testing outside the NixOS project</title>
+      <para>
+        Outside the <literal>nixpkgs</literal> repository, you can
+        instantiate the test by first importing the NixOS library,
+      </para>
+      <programlisting language="bash">
+let nixos-lib = import (nixpkgs + &quot;/nixos/lib&quot;) { };
+in
+
+nixos-lib.runTest {
+  imports = [ ./test.nix ];
+  hostPkgs = pkgs;  # the Nixpkgs package set used outside the VMs
+  defaults.services.foo.package = mypkg;
+}
+</programlisting>
+      <para>
+        <literal>runTest</literal> returns a derivation that runs the
+        test.
+      </para>
+    </section>
+  </section>
+  <section xml:id="sec-nixos-test-nodes">
+    <title>Configuring the nodes</title>
+    <para>
+      There are a few special NixOS 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">
+    <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>
   <section xml:id="ssec-machine-objects">
     <title>Machine objects</title>
     <para>
@@ -194,8 +262,9 @@ start_all()
           </para>
           <note>
             <para>
-              This requires passing <literal>enableOCR</literal> to the
-              test attribute set.
+              This requires
+              <link linkend="test-opt-enableOCR"><literal>enableOCR</literal></link>
+              to be set to <literal>true</literal>.
             </para>
           </note>
         </listitem>
@@ -211,8 +280,9 @@ start_all()
           </para>
           <note>
             <para>
-              This requires passing <literal>enableOCR</literal> to the
-              test attribute set.
+              This requires
+              <link linkend="test-opt-enableOCR"><literal>enableOCR</literal></link>
+              to be set to <literal>true</literal>.
             </para>
           </note>
         </listitem>
@@ -451,8 +521,9 @@ start_all()
           </para>
           <note>
             <para>
-              This requires passing <literal>enableOCR</literal> to the
-              test attribute set.
+              This requires
+              <link linkend="test-opt-enableOCR"><literal>enableOCR</literal></link>
+              to be set to <literal>true</literal>.
             </para>
           </note>
         </listitem>
@@ -465,7 +536,7 @@ start_all()
           <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.
+            not possible or accurate enough.
           </para>
         </listitem>
       </varlistentry>
@@ -560,10 +631,10 @@ machine.wait_for_unit(&quot;xautolock.service&quot;, &quot;x-session-user&quot;)
     </para>
     <para>
       For faster dev cycles it's also possible to disable the
-      code-linters (this shouldn't be commited though):
+      code-linters (this shouldn't be committed though):
     </para>
     <programlisting language="bash">
-import ./make-test-python.nix {
+{
   skipLint = true;
   nodes.machine =
     { config, pkgs, ... }:
@@ -590,11 +661,24 @@ import ./make-test-python.nix {
       # fmt: on
     '';
 </programlisting>
+    <para>
+      Similarly, the type checking of test scripts can be disabled in
+      the following way:
+    </para>
+    <programlisting language="bash">
+{
+  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 invariables are no longer met
+      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
@@ -622,12 +706,10 @@ with foo_running:
     <para>
       : specifies how often the condition should be polled:
     </para>
-    <programlisting>
-```py
+    <programlisting language="python">
 @polling_condition(seconds_interval=10)
 def foo_running():
     machine.succeed(&quot;pgrep -x foo&quot;)
-```
 </programlisting>
     <para>
       <literal>description</literal>
@@ -637,19 +719,51 @@ def foo_running():
       provided, the description is pulled from the docstring of the
       function. These two are therefore equivalent:
     </para>
-    <programlisting>
-```py
+    <programlisting language="python">
 @polling_condition
 def foo_running():
     &quot;check that foo is running&quot;
     machine.succeed(&quot;pgrep -x foo&quot;)
-```
-
-```py
+</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">
+{
+  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 xml:id="sec-test-options-reference">
+    <title>Test Options Reference</title>
+    <para>
+      The following options can be used when writing tests.
+    </para>
+    <xi:include href="../../generated/test-options-db.xml" xpointer="test-options-list"/>
+  </section>
 </section>
diff --git a/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml b/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
index e7a76a6d715d..080f1535e410 100644
--- a/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
+++ b/nixos/doc/manual/from_md/installation/building-nixos.chapter.xml
@@ -24,7 +24,7 @@
   </itemizedlist>
   <para>
     System images, such as the live installer ones, know how to enforce
-    configuration settings on wich they immediately depend in order to
+    configuration settings on which they immediately depend in order to
     work correctly.
   </para>
   <para>
@@ -33,9 +33,14 @@
   </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>
@@ -97,7 +102,7 @@ $ nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd
       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
+      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/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml b/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml
index 525531a47813..f29200952ac5 100644
--- a/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml
+++ b/nixos/doc/manual/from_md/installation/installing-from-other-distro.section.xml
@@ -211,7 +211,7 @@ $ sudo groupdel nixbld
         Generate your NixOS configuration:
       </para>
       <programlisting>
-$ sudo `which nixos-generate-config` --root /
+$ sudo `which nixos-generate-config`
 </programlisting>
       <para>
         Note that this will place the generated configuration files in
@@ -223,7 +223,7 @@ $ sudo `which nixos-generate-config` --root /
       <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
+        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 <literal>sudo passwd -l root</literal> if you use
@@ -248,7 +248,7 @@ $ nix-env -p /nix/var/nix/profiles/system -f '&lt;nixpkgs/nixos&gt;' -I nixos-co
         (since your Nix install was probably single user):
       </para>
       <programlisting>
-$ sudo chown -R 0.0 /nix
+$ sudo chown -R 0:0 /nix
 </programlisting>
     </listitem>
     <listitem>
diff --git a/nixos/doc/manual/from_md/installation/installing-kexec.section.xml b/nixos/doc/manual/from_md/installation/installing-kexec.section.xml
new file mode 100644
index 000000000000..46ea0d59b6c3
--- /dev/null
+++ b/nixos/doc/manual/from_md/installation/installing-kexec.section.xml
@@ -0,0 +1,94 @@
+<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/nixos/doc/manual/from_md/installation/installing-usb.section.xml b/nixos/doc/manual/from_md/installation/installing-usb.section.xml
index df266eb16800..9d12ac45aac2 100644
--- a/nixos/doc/manual/from_md/installation/installing-usb.section.xml
+++ b/nixos/doc/manual/from_md/installation/installing-usb.section.xml
@@ -1,35 +1,135 @@
 <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>
+  <title>Booting from a USB flash 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.
+    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.
   </para>
-  <note>
-    <title>On macOS</title>
+  <section xml:id="sec-booting-from-usb-graphical">
+    <title>Creating bootable USB flash drive with a graphical
+    tool</title>
+    <para>
+      Etcher is a popular and user-friendly tool. It works on Linux,
+      Windows and macOS.
+    </para>
+    <para>
+      Download it from
+      <link xlink:href="https://www.balena.io/etcher/">balena.io</link>,
+      start the program, select the downloaded NixOS ISO, then select
+      the USB flash drive and flash it.
+    </para>
+    <warning>
+      <para>
+        Etcher reports errors and usage statistics by default, which can
+        be disabled in the settings.
+      </para>
+    </warning>
+    <para>
+      An alternative is
+      <link xlink:href="https://bztsrc.gitlab.io/usbimager">USBImager</link>,
+      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 <quote>Write</quote>.
+    </para>
+  </section>
+  <section xml:id="sec-booting-from-usb-linux">
+    <title>Creating bootable USB flash drive from a Terminal on
+    Linux</title>
+    <orderedlist numeration="arabic" spacing="compact">
+      <listitem>
+        <para>
+          Plug in the USB flash drive.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Find the corresponding device with <literal>lsblk</literal>.
+          You can distinguish them by their size.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Make sure all partitions on the device are properly unmounted.
+          Replace <literal>sdX</literal> with your device (e.g.
+          <literal>sdb</literal>).
+        </para>
+      </listitem>
+    </orderedlist>
+    <programlisting>
+sudo umount /dev/sdX*
+</programlisting>
+    <orderedlist numeration="arabic" spacing="compact">
+      <listitem override="4">
+        <para>
+          Then use the <literal>dd</literal> utility to write the image
+          to the USB flash drive.
+        </para>
+      </listitem>
+    </orderedlist>
     <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
+sudo dd if=&lt;path-to-image&gt; of=/dev/sdX bs=4M conv=fsync
+</programlisting>
+  </section>
+  <section xml:id="sec-booting-from-usb-macos">
+    <title>Creating bootable USB flash drive from a Terminal on
+    macOS</title>
+    <orderedlist numeration="arabic" spacing="compact">
+      <listitem>
+        <para>
+          Plug in the USB flash drive.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Find the corresponding device with
+          <literal>diskutil list</literal>. You can distinguish them by
+          their size.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Make sure all partitions on the device are properly unmounted.
+          Replace <literal>diskX</literal> with your device (e.g.
+          <literal>disk1</literal>).
+        </para>
+      </listitem>
+    </orderedlist>
+    <programlisting>
+diskutil unmountDisk diskX
+</programlisting>
+    <orderedlist numeration="arabic" spacing="compact">
+      <listitem override="4">
+        <para>
+          Then use the <literal>dd</literal> utility to write the image
+          to the USB flash drive.
+        </para>
+      </listitem>
+    </orderedlist>
+    <programlisting>
+sudo dd if=&lt;path-to-image&gt; of=/dev/rdiskX bs=4m
 </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>
+    <note>
+      <para>
+        Using the 'raw' <literal>rdiskX</literal> device instead of
+        <literal>diskX</literal> with dd completes in minutes instead of
+        hours.
+      </para>
+    </note>
+    <orderedlist numeration="arabic" spacing="compact">
+      <listitem override="5">
+        <para>
+          Eject the disk when it is finished.
+        </para>
+      </listitem>
+    </orderedlist>
+    <programlisting>
+diskutil eject /dev/diskX
+</programlisting>
+  </section>
 </section>
diff --git a/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml b/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml
index c8bb286c8f33..8b82a617e7f5 100644
--- a/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml
+++ b/nixos/doc/manual/from_md/installation/installing-virtualbox-guest.section.xml
@@ -1,4 +1,4 @@
-<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-instaling-virtualbox-guest">
+<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-installing-virtualbox-guest">
   <title>Installing in a VirtualBox guest</title>
   <para>
     Installing NixOS into a VirtualBox guest is convenient for users who
diff --git a/nixos/doc/manual/from_md/installation/installing.chapter.xml b/nixos/doc/manual/from_md/installation/installing.chapter.xml
index aee0b30a7076..c8d1e26b5e77 100644
--- a/nixos/doc/manual/from_md/installation/installing.chapter.xml
+++ b/nixos/doc/manual/from_md/installation/installing.chapter.xml
@@ -1,26 +1,212 @@
 <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>
+    <title>Booting from the install medium</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.
+      To begin the installation, you have to boot your computer from the
+      install drive.
     </para>
+    <orderedlist numeration="arabic">
+      <listitem>
+        <para>
+          Plug in the install drive. Then turn on or restart your
+          computer.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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
+          <quote>USB</quote>). 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.
+        </para>
+        <note>
+          <para>
+            The key to open the boot menu is different across computer
+            brands and even models. It can be <keycap>F12</keycap>, but
+            also <keycap>F1</keycap>, <keycap>F9</keycap>,
+            <keycap>F10</keycap>, <keycap>Enter</keycap>,
+            <keycap>Del</keycap>, <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 <quote>boot from usb</quote>. 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.
+          </para>
+          <para>
+            For Apple computers with Intel processors press and hold the
+            <keycap>⌥</keycap> (Option or Alt) key until you see the
+            boot menu. On Apple silicon press and hold the power button.
+          </para>
+        </note>
+        <note>
+          <para>
+            If your computer supports both BIOS and UEFI boot, choose
+            the UEFI option.
+          </para>
+        </note>
+        <note>
+          <para>
+            If you use a CD for the installation, the computer will
+            probably boot from it automatically. If not, choose the
+            option containing the word <quote>CD</quote> from the boot
+            menu.
+          </para>
+        </note>
+      </listitem>
+      <listitem>
+        <para>
+          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 <keycap>Enter</keycap> to
+          speed up).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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
+          <xref linkend="sec-installation-manual" /> there.
+        </para>
+      </listitem>
+    </orderedlist>
+  </section>
+  <section xml:id="sec-installation-graphical">
+    <title>Graphical Installation</title>
     <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" />).
+      The graphical installer is recommended for desktop users and will
+      guide you through the installation.
     </para>
+    <orderedlist numeration="arabic">
+      <listitem>
+        <para>
+          In the <quote>Welcome</quote> screen, you can select the
+          language of the Installer and the installed system.
+        </para>
+        <tip>
+          <para>
+            Leaving the language as <quote>American English</quote> will
+            make it easier to search for error messages in a search
+            engine or to report an issue.
+          </para>
+        </tip>
+      </listitem>
+      <listitem>
+        <para>
+          Next you should choose your location to have the timezone set
+          correctly. You can actually click on the map!
+        </para>
+        <note>
+          <para>
+            The installer will use an online service to guess your
+            location based on your public IP address.
+          </para>
+        </note>
+      </listitem>
+      <listitem>
+        <para>
+          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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          On the <quote>Users</quote> 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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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 <quote>No desktop</quote>.
+        </para>
+        <tip>
+          <para>
+            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.
+          </para>
+        </tip>
+      </listitem>
+      <listitem>
+        <para>
+          You have the option to allow unfree software in the next
+          screen.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The easiest option in the <quote>Partitioning</quote> screen
+          is <quote>Erase disk</quote>, which will delete all data from
+          the selected disk and install the system on it. Also select
+          <quote>Swap (with Hibernation)</quote> in the dropdown below
+          it. You have the option to encrypt the whole disk with LUKS.
+        </para>
+        <note>
+          <para>
+            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 <quote>BIOS</quote>, reboot with the correct option.
+          </para>
+        </note>
+        <warning>
+          <para>
+            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.
+          </para>
+        </warning>
+      </listitem>
+      <listitem>
+        <para>
+          Check the choices you made in the <quote>Summary</quote> and
+          click <quote>Install</quote>.
+        </para>
+        <note>
+          <para>
+            The installation takes about 15 minutes. The time varies
+            based on the selected desktop environment, internet
+            connection speed and disk write speed.
+          </para>
+        </note>
+      </listitem>
+      <listitem>
+        <para>
+          When the install is complete, remove the USB flash drive and
+          reboot into your new system!
+        </para>
+      </listitem>
+    </orderedlist>
+  </section>
+  <section xml:id="sec-installation-manual">
+    <title>Manual Installation</title>
     <para>
-      The installation media contains a basic NixOS installation. When
-      it’s finished booting, it should have detected most of your
-      hardware.
+      NixOS can be installed on BIOS or UEFI systems. The procedure for
+      a UEFI installation is broadly the same as for a BIOS
+      installation. The differences are mentioned in the following
+      steps.
     </para>
     <para>
       The NixOS manual is available by running
-      <literal>nixos-help</literal>.
+      <literal>nixos-help</literal> in the command line or from the
+      application menu in the desktop environment.
+    </para>
+    <para>
+      To have access to the command line on the graphical images, open
+      Terminal (GNOME) or Konsole (Plasma) from the application menu.
     </para>
     <para>
       You are logged-in automatically as <literal>nixos</literal>. The
@@ -31,11 +217,8 @@
 $ 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
+      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>
@@ -49,9 +232,13 @@ $ sudo -i
       bootloader lists boot entries, select the serial console boot
       entry.
     </para>
-    <section xml:id="sec-installation-booting-networking">
+    <section xml:id="sec-installation-manual-networking">
       <title>Networking in the installer</title>
       <para>
+        <anchor xml:id="sec-installation-booting-networking" />
+        <!-- legacy anchor -->
+      </para>
+      <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
@@ -69,7 +256,7 @@ $ sudo -i
       </para>
       <para>
         On the minimal installer, NetworkManager is not available, so
-        configuration must be perfomed manually. To configure the wifi,
+        configuration must be performed 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
@@ -130,474 +317,527 @@ OK
         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>
+    <section xml:id="sec-installation-manual-partitioning">
+      <title>Partitioning and formatting</title>
+      <para>
+        <anchor xml:id="sec-installation-partitioning" />
+        <!-- legacy anchor -->
+      </para>
+      <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>
-        Here's an example partition scheme for UEFI, using
-        <literal>/dev/sda</literal> as the device.
+        The recommended partition scheme differs depending if the
+        computer uses <emphasis>Legacy Boot</emphasis> or
+        <emphasis>UEFI</emphasis>.
       </para>
-      <note>
+      <section xml:id="sec-installation-manual-partitioning-UEFI">
+        <title>UEFI (GPT)</title>
         <para>
-          You can safely ignore <literal>parted</literal>'s
-          informational message about needing to update /etc/fstab.
+          <anchor xml:id="sec-installation-partitioning-UEFI" />
+          <!-- legacy anchor -->
         </para>
-      </note>
-      <orderedlist numeration="arabic">
-        <listitem>
+        <para>
+          Here's an example partition scheme for UEFI, using
+          <literal>/dev/sda</literal> as the device.
+        </para>
+        <note>
           <para>
-            Create a <emphasis>GPT</emphasis> partition table.
+            You can safely ignore <literal>parted</literal>'s
+            informational message about needing to update /etc/fstab.
           </para>
-          <programlisting>
+        </note>
+        <orderedlist numeration="arabic">
+          <listitem>
+            <para>
+              Create a <emphasis>GPT</emphasis> partition table.
+            </para>
+            <programlisting>
 # parted /dev/sda -- mklabel gpt
 </programlisting>
-        </listitem>
-        <listitem>
-          <para>
-            Add the <emphasis>root</emphasis> partition. This will fill
-            the disk except for the end part, where the swap will live,
-            and the space left in front (512MiB) which will be used by
-            the boot partition.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mkpart primary 512MiB -8GiB
+          </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 8GiB one is
-            created.
-          </para>
-          <programlisting>
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+          </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>
+            <note>
+              <para>
+                The swap partition size rules are no different than for
+                other Linux distributions.
+              </para>
+            </note>
+          </listitem>
+          <listitem>
             <para>
-              The swap partition size rules are no different than for
-              other Linux distributions.
+              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>
-          </note>
-        </listitem>
-        <listitem>
+            <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-manual-partitioning-formatting" />.
+        </para>
+      </section>
+      <section xml:id="sec-installation-manual-partitioning-MBR">
+        <title>Legacy Boot (MBR)</title>
+        <para>
+          <anchor xml:id="sec-installation-partitioning-MBR" />
+          <!-- legacy anchor -->
+        </para>
+        <para>
+          Here's an example partition scheme for Legacy Boot, using
+          <literal>/dev/sda</literal> as the device.
+        </para>
+        <note>
           <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.
+            You can safely ignore <literal>parted</literal>'s
+            informational message about needing to update /etc/fstab.
           </para>
-          <programlisting>
-# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
-# parted /dev/sda -- set 3 esp on
+        </note>
+        <orderedlist numeration="arabic">
+          <listitem>
+            <para>
+              Create a <emphasis>MBR</emphasis> partition table.
+            </para>
+            <programlisting>
+# parted /dev/sda -- mklabel msdos
 </programlisting>
-        </listitem>
-      </orderedlist>
-      <para>
-        Once complete, you can follow with
-        <xref linkend="sec-installation-partitioning-formatting" />.
-      </para>
+          </listitem>
+          <listitem>
+            <para>
+              Add the <emphasis>root</emphasis> partition. This will
+              fill the the disk except for the end part, where the swap
+              will live.
+            </para>
+            <programlisting>
+# parted /dev/sda -- mkpart primary 1MB -8GB
+</programlisting>
+          </listitem>
+          <listitem>
+            <para>
+              Set the root partition’s boot flag to on. This allows the
+              disk to be booted from.
+            </para>
+            <programlisting>
+# parted /dev/sda -- set 1 boot on
+</programlisting>
+          </listitem>
+          <listitem>
+            <para>
+              Finally, 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>
+        </orderedlist>
+        <para>
+          Once complete, you can follow with
+          <xref linkend="sec-installation-manual-partitioning-formatting" />.
+        </para>
+      </section>
+      <section xml:id="sec-installation-manual-partitioning-formatting">
+        <title>Formatting</title>
+        <para>
+          <anchor xml:id="sec-installation-partitioning-formatting" />
+          <!-- legacy anchor -->
+        </para>
+        <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-partitioning-MBR">
-      <title>Legacy Boot (MBR)</title>
+    <section xml:id="sec-installation-manual-installing">
+      <title>Installing</title>
       <para>
-        Here's an example partition scheme for Legacy Boot, using
-        <literal>/dev/sda</literal> as the device.
+        <anchor xml:id="sec-installation-installing" />
+        <!-- legacy anchor -->
       </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.
+            Mount the target file system on which NixOS should be
+            installed on <literal>/mnt</literal>, e.g.
           </para>
           <programlisting>
-# parted /dev/sda -- mklabel msdos
+# mount /dev/disk/by-label/nixos /mnt
 </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.
+            <emphasis role="strong">UEFI systems</emphasis>
+          </para>
+          <para>
+            Mount the boot file system on <literal>/mnt/boot</literal>,
+            e.g.
           </para>
           <programlisting>
-# parted /dev/sda -- mkpart primary 1MiB -8GiB
+# mkdir -p /mnt/boot
+# mount /dev/disk/by-label/boot /mnt/boot
 </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.
+            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>
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+# swapon /dev/sda2
 </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:
+            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>
-# mkfs.ext4 -L nixos /dev/sda1
+# nixos-generate-config --root /mnt
 </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:
+            You should then edit
+            <literal>/mnt/etc/nixos/configuration.nix</literal> to suit
+            your needs:
           </para>
           <programlisting>
-# mkswap -L swap /dev/sda2
+# nano /mnt/etc/nixos/configuration.nix
 </programlisting>
-        </listitem>
-        <listitem>
           <para>
-            <emphasis role="strong">UEFI systems</emphasis>
+            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>
-            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:
+            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>
-          <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>.
+            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>
-            For creating software RAID devices, use
-            <literal>mdadm</literal>.
+            Do the installation:
           </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>
+# nixos-install
 </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 <emphasis>must</emphasis> set the option
-                <xref linkend="opt-boot.loader.systemd-boot.enable" />
-                to <literal>true</literal>.
-                <literal>nixos-generate-config</literal> should do this
-                automatically for new configurations when booted in UEFI
-                mode.
-              </para>
-              <para>
-                You may want to look at the options starting with
-                <link linkend="opt-boot.loader.efi.canTouchEfiVariables"><literal>boot.loader.efi</literal></link>
-                and
-                <link linkend="opt-boot.loader.systemd-boot.enable"><literal>boot.loader.systemd-boot</literal></link>
-                as well.
-              </para>
-            </listitem>
-          </varlistentry>
-        </variablelist>
-        <para>
-          If 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.
+            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>
-        </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>
+          <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>
+          <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>
-            For unattended installations, it is possible to use
-            <literal>nixos-install --no-root-passwd</literal> in order
-            to disable the password prompt entirely.
+            If everything went well:
           </para>
-        </note>
-      </listitem>
-      <listitem>
-        <para>
-          If everything went well:
-        </para>
-        <programlisting>
+          <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>
+        </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>
+          <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-manual-summary">
+      <title>Installation summary</title>
+      <para>
+        <anchor xml:id="sec-installation-summary" />
+        <!-- legacy anchor -->
+      </para>
+      <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%
+# parted /dev/sda -- mkpart primary 1MB -8GB
+# parted /dev/sda -- mkpart primary linux-swap -8GB 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>
+      <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 -- 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
 </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>
+      <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
@@ -610,11 +850,11 @@ $ passwd eelco
 # nixos-install
 # reboot
 </programlisting>
-    <anchor xml:id="ex-config" />
-    <para>
-      <emphasis role="strong">Example: NixOS Configuration</emphasis>
-    </para>
-    <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.
@@ -633,11 +873,13 @@ $ passwd eelco
   services.sshd.enable = true;
 }
 </programlisting>
+    </section>
   </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" />
diff --git a/nixos/doc/manual/from_md/installation/obtaining.chapter.xml b/nixos/doc/manual/from_md/installation/obtaining.chapter.xml
index a922feda2536..d187adfc0c53 100644
--- a/nixos/doc/manual/from_md/installation/obtaining.chapter.xml
+++ b/nixos/doc/manual/from_md/installation/obtaining.chapter.xml
@@ -2,16 +2,15 @@
   <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>.
+    <link xlink:href="https://nixos.org/download.html#nixos-iso">NixOS
+    download page</link>. Follow the instructions in
+    <xref linkend="sec-booting-from-usb" /> to create a bootable USB
+    flash drive.
+  </para>
+  <para>
+    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.
   </para>
   <para>
     As an alternative to installing NixOS yourself, you can get a
@@ -23,16 +22,16 @@
         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
+        <link xlink:href="https://nixos.org/download.html#nixos-virtualbox">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>.
+        Using AMIs for Amazon’s EC2. To find one for your region, please
+        refer to the
+        <link xlink:href="https://nixos.org/download.html#nixos-amazon">download
+        page</link>.
       </para>
     </listitem>
     <listitem>
diff --git a/nixos/doc/manual/from_md/installation/upgrading.chapter.xml b/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
index e3b77d4c3650..f6aedc800aca 100644
--- a/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
+++ b/nixos/doc/manual/from_md/installation/upgrading.chapter.xml
@@ -12,7 +12,7 @@
     <listitem>
       <para>
         <emphasis>Stable channels</emphasis>, such as
-        <link xlink:href="https://nixos.org/channels/nixos-21.11"><literal>nixos-21.11</literal></link>.
+        <link xlink:href="https://nixos.org/channels/nixos-22.05"><literal>nixos-22.11</literal></link>.
         These only get conservative bug fixes and package upgrades. For
         instance, a channel update may cause the Linux kernel on your
         system to be upgraded from 4.19.34 to 4.19.38 (a minor bug fix),
@@ -33,7 +33,7 @@
     <listitem>
       <para>
         <emphasis>Small channels</emphasis>, such as
-        <link xlink:href="https://nixos.org/channels/nixos-21.11-small"><literal>nixos-21.11-small</literal></link>
+        <link xlink:href="https://nixos.org/channels/nixos-22.05-small"><literal>nixos-22.11-small</literal></link>
         or
         <link xlink:href="https://nixos.org/channels/nixos-unstable-small"><literal>nixos-unstable-small</literal></link>.
         These are identical to the stable and unstable channels
@@ -60,8 +60,8 @@
   <para>
     When you first install NixOS, you’re automatically subscribed to the
     NixOS channel that corresponds to your installation source. For
-    instance, if you installed from a 21.11 ISO, you will be subscribed
-    to the <literal>nixos-21.11</literal> channel. To see which NixOS
+    instance, if you installed from a 22.11 ISO, you will be subscribed
+    to the <literal>nixos-22.11</literal> channel. To see which NixOS
     channel you’re subscribed to, run the following as root:
   </para>
   <programlisting>
@@ -76,17 +76,17 @@ nixos https://nixos.org/channels/nixos-unstable
 </programlisting>
   <para>
     (Be sure to include the <literal>nixos</literal> parameter at the
-    end.) For instance, to use the NixOS 21.11 stable channel:
+    end.) For instance, to use the NixOS 22.11 stable channel:
   </para>
   <programlisting>
-# nix-channel --add https://nixos.org/channels/nixos-21.11 nixos
+# nix-channel --add https://nixos.org/channels/nixos-22.11 nixos
 </programlisting>
   <para>
     If you have a server, you may want to use the <quote>small</quote>
     channel instead:
   </para>
   <programlisting>
-# nix-channel --add https://nixos.org/channels/nixos-21.11-small nixos
+# nix-channel --add https://nixos.org/channels/nixos-22.11-small nixos
 </programlisting>
   <para>
     And if you want to live on the bleeding edge:
@@ -146,7 +146,7 @@ system.autoUpgrade.allowReboot = true;
       also specify a channel explicitly, e.g.
     </para>
     <programlisting language="bash">
-system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.11;
+system.autoUpgrade.channel = https://nixos.org/channels/nixos-22.11;
 </programlisting>
   </section>
 </chapter>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml b/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml
index 172b800b5992..afbd2fd2c797 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-1603.section.xml
@@ -545,7 +545,7 @@ nginx.override {
         <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
+        apply after others, so their results wouldn't be overridden by
         anything else.
       </para>
     </listitem>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml b/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml
index 8f0efe816e51..fc5d11f07c8d 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-1709.section.xml
@@ -666,7 +666,7 @@ rmdir /var/lib/ipfs/.ipfs
       <listitem>
         <para>
           <literal>services.firefox.syncserver</literal> now runs by
-          default as a non-root user. To accomodate this change, the
+          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.
diff --git a/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml b/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml
index f26e68e13200..31c5c1fc7f49 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-1903.section.xml
@@ -271,7 +271,7 @@
       <listitem>
         <para>
           The versioned <literal>postgresql</literal> have been renamed
-          to use underscore number seperators. For example,
+          to use underscore number separators. For example,
           <literal>postgresql96</literal> has been renamed to
           <literal>postgresql_9_6</literal>.
         </para>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml b/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml
index 83cd649f4ea0..f9b99961d277 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-1909.section.xml
@@ -498,7 +498,7 @@
       <listitem>
         <para>
           The <literal>prometheus-nginx-exporter</literal> package now
-          uses the offical exporter provided by NGINX Inc. Its metrics
+          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
           <link xlink:href="https://github.com/nginxinc/nginx-prometheus-exporter">official
@@ -524,7 +524,7 @@
         <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
+          real user, now run under a separate user and group which
           follow the pattern
           <literal>&lt;exporter-name&gt;-exporter</literal>, instead of
           the previous default <literal>nobody</literal> and
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml
index fb11b19229e2..3477f29f4281 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2105.section.xml
@@ -1304,7 +1304,7 @@ self: super:
       <listitem>
         <para>
           In the ACME module, the data used to build the hash for the
-          account directory has changed to accomodate new features to
+          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
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index b61a0268dee2..9b6e755fd470 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -561,6 +561,14 @@
           <link xlink:href="options.html#opt-services.prometheus.exporters.smartctl.enable">services.prometheus.exporters.smartctl</link>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://docs.twingate.com/docs/linux">twingate</link>,
+          a high performance, easy to use zero trust solution that
+          enables access to private resources from any device with
+          better security than a VPN.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-incompatibilities">
@@ -569,8 +577,9 @@
       <listitem>
         <para>
           The NixOS VM test framework,
-          <literal>pkgs.nixosTest</literal>/<literal>make-test-python.nix</literal>,
-          now requires detaching commands such as
+          <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
@@ -1434,7 +1443,7 @@ Superuser created successfully.
         <para>
           The default GNAT version has been changed: The
           <literal>gnat</literal> attribute now points to
-          <literal>gnat11</literal> instead of <literal>gnat9</literal>.
+          <literal>gnat12</literal> instead of <literal>gnat9</literal>.
         </para>
       </listitem>
       <listitem>
@@ -1468,6 +1477,16 @@ Superuser created successfully.
           extent.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <literal>pkgs.haskell-language-server</literal> 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.
+          <literal>pkgs.haskell-language-server.override { supportedGhcVersions = [ &quot;90&quot; &quot;92&quot; ]; }</literal>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-21.11-notable-changes">
@@ -2086,6 +2105,18 @@ Superuser created successfully.
           <literal>java-packages.compiler</literal>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          The sets <literal>haskell.packages</literal> and
+          <literal>haskell.compiler</literal> now contain for every ghc
+          version an attribute with the minor version dropped. E.g. for
+          <literal>ghc8107</literal> there also now exists
+          <literal>ghc810</literal>. Those attributes point to the same
+          compilers and packagesets but have the advantage that e.g.
+          <literal>ghc92</literal> stays stable when we update from
+          <literal>ghc925</literal> to <literal>ghc926</literal>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 05b3822cab71..c43757a9a057 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -1,9 +1,5 @@
 <section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-22.05">
-  <title>Release 22.05 (“Quokka”, 2022.05/??)</title>
-  <para>
-    In addition to numerous new and upgraded packages, this release has
-    the following highlights:
-  </para>
+  <title>Release 22.05 (“Quokka”, 2022.05/30)</title>
   <itemizedlist spacing="compact">
     <listitem>
       <para>
@@ -14,50 +10,36 @@
   </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>
-        <para>
-          The <literal>firefox</literal> browser on
-          <literal>x86_64-linux</literal> is now making use of
-          profile-guided optimization resulting in a much more
-          responsive browsing experience.
-        </para>
+<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>
-          <literal>security.acme.defaults</literal> has been added to
-          simplify configuring settings for many certificates at once.
-          This also opens up the the option to use DNS-01 validation
-          when using <literal>enableACME</literal> on web server virtual
-          hosts (e.g.
-          <literal>services.nginx.virtualHosts.*.enableACME</literal>).
+          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. Notably, it replaces gedit with
-          GNOME Text Editor, GNOME Terminal with GNOME Console (formerly
-          King’s Cross), and GNOME Screenshot with a tool built into the
-          Shell.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
-          PHP 8.1 is now available
+          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>
-          Mattermost has been updated to extended support release 6.3,
-          as the previously packaged extended support release 5.37 is
-          <link xlink:href="https://docs.mattermost.com/upgrade/extended-support-release.html">reaching
-          its end of life</link>. Migrations may take a while, 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>.
+          PHP 8.1 is now available.
         </para>
       </listitem>
       <listitem>
@@ -75,21 +57,22 @@
       </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.
+          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>
-          <link xlink:href="https://kops.sigs.k8s.io"><literal>kops</literal></link>
-          defaults to 1.22.4, 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 1.22. This will increase security by
-          default, but may break some types of workloads. See the
-          <link xlink:href="https://kops.sigs.k8s.io/releases/1.22-notes/">release
-          notes</link> for details.
+          PostgreSQL now defaults to major version 14.
         </para>
       </listitem>
       <listitem>
@@ -108,6 +91,24 @@
           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">
@@ -115,6 +116,16 @@
     <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
@@ -123,39 +134,109 @@
       </listitem>
       <listitem>
         <para>
-          <link xlink:href="https://docs.docker.com/engine/security/rootless/">rootless
-          Docker</link>, a <literal>systemd --user</literal> Docker
-          service which runs without root permissions. Available as
-          <link xlink:href="options.html#opt-virtualisation.docker.rootless.enable">virtualisation.docker.rootless.enable</link>.
+          <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://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>.
+          <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://github.com/vvilhonen/nethoscope">nethoscope</link>,
-          listen to your network traffic. Available as
-          <link linkend="opt-programs.nethoscope.enable">programs.nethoscope</link>.
+          <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://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>.
+          <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://github.com/linux-apfs/linux-apfs-rw">apfs</link>,
-          a kernel module for mounting the Apple File System (APFS).
+          <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>
@@ -163,42 +244,46 @@
           <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>
+          <link linkend="opt-services.frr.babel.enable">services.frr</link>.
         </para>
       </listitem>
       <listitem>
         <para>
-          <link xlink:href="https://github.com/hifi/heisenbridge">heisenbridge</link>,
-          a bouncer-style Matrix IRC bridge. Available as
-          <link xlink:href="options.html#opt-services.heisenbridge.enable">services.heisenbridge</link>.
+          <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://snowflake.torproject.org/">snowflake-proxy</link>,
-          a system to defeat internet censorship. Available as
-          <link xlink:href="options.html#opt-services.snowflake-proxy.enable">services.snowflake-proxy</link>.
+          <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://ergo.chat">ergochat</link>, a modern
-          IRC with IRCv3 features. Available as
-          <link xlink:href="options.html#opt-services.ergochat.enable">services.ergochat</link>.
+          <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/ngoduykhanh/PowerDNS-Admin">PowerDNS-Admin</link>,
-          a web interface for the PowerDNS server. Available at
-          <link xlink:href="options.html#opt-services.powerdns-admin.enable">services.powerdns-admin</link>.
+          <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/postgres/pgadmin4">pgadmin4</link>,
-          an admin interface for the PostgreSQL database. Available at
-          <link xlink:href="options.html#opt-services.pgadmin.enable">services.pgadmin</link>.
+          <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>
@@ -206,7 +291,7 @@
           <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 xlink:href="options.html#opt-services.input-remapper.enable">services.input-remapper</link>.
+          <link linkend="opt-services.input-remapper.enable">services.input-remapper</link>.
         </para>
       </listitem>
       <listitem>
@@ -214,101 +299,133 @@
           <link xlink:href="https://invoiceplane.com">InvoicePlane</link>,
           web application for managing and creating invoices. Available
           at
-          <link xlink:href="options.html#opt-services.invoiceplane.enable">services.invoiceplane</link>.
+          <link linkend="opt-services.invoiceplane.sites._name_.enable">services.invoiceplane</link>.
         </para>
       </listitem>
       <listitem>
         <para>
-          <link xlink:href="https://maddy.email">maddy</link>, a
-          composable all-in-one mail server. Available as
-          <link xlink:href="options.html#opt-services.maddy.enable">services.maddy</link>.
+          <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 xlink:href="options.html#opt-programs.k4-whisperer.enable">programs.k40-whisperer.enable</link>.
+          <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. Available 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 xlink:href="options.html#opt-services.mozillavpn">services.mozillavpn</link>.
+          <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 xlink:href="options.html#opt-services.mtr-exporter.enable">services.mtr-exporter</link>.
+          <link linkend="opt-services.mtr-exporter.enable">services.mtr-exporter</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 xlink:href="options.html#opt-services.prometheus.exporters.pve">services.prometheus.exporters.pve</link>.
+          <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 xlink:href="options.html#opt-services.netbox.enable">services.netbox</link>.
+          <link linkend="opt-services.netbox.enable">services.netbox</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>.
+          <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://github.com/mbrubeck/agate">agate</link>,
-          a very simple server for the Gemini hypertext protocol.
-          Available as
-          <link xlink:href="options.html#opt-services.agate.enable">services.agate</link>.
+          <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/JustArchiNET/ArchiSteamFarm">ArchiSteamFarm</link>,
-          a C# application with primary purpose of idling Steam cards
-          from multiple accounts simultaneously. Available as
-          <link xlink:href="options.html#opt-services.archisteamfarm.enable">services.archisteamfarm</link>.
+          <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="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>.
+          <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://loic-sharma.github.io/BaGet/">BaGet</link>,
-          a lightweight NuGet and symbol server. Available at
-          <link linkend="opt-services.baget.enable">services.baget</link>.
+          <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://moosefs.com">moosefs</link>, fault
-          tolerant petabyte distributed file system. Available as
-          <link linkend="opt-services.moosefs.client.enable">moosefs</link>.
+          <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>
@@ -320,88 +437,145 @@
       </listitem>
       <listitem>
         <para>
-          <link xlink:href="https://github.com/rfjakob/systembus-notify">systembus-notify</link>,
-          allow system level notifications to reach the users. Available
+          <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 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.
+          <link linkend="opt-services.snipe-it.enable">services.snipe-it</link>.
         </para>
       </listitem>
       <listitem>
         <para>
-          <link xlink:href="https://github.com/audreyt/ethercalc">ethercalc</link>,
-          an online collaborative spreadsheet. Available as
-          <link xlink:href="options.html#opt-services.ethercalc.enable">services.ethercalc</link>.
+          <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://nbd.sourceforge.io/">nbd</link>, a
-          Network Block Device server. Available as
-          <link xlink:href="options.html#opt-services.nbd.server.enable">services.nbd</link>.
+          <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://github.com/Mic92/nix-ld">nix-ld</link>,
-          Run unpatched dynamic binaries on NixOS. Available as
-          <link xlink:href="options.html#opt-programs.nix-ld.enable">programs.nix-ld</link>.
+          <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://timetagger.app">timetagger</link>,
-          an open source time-tracker with an intuitive user experience
-          and powerful reporting.
-          <link xlink:href="options.html#opt-services.timetagger.enable">services.timetagger</link>.
+          <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://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 xlink:href="options.html#opt-services.rstudio-server.enable">services.rstudio-server</link>.
+          <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://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 xlink:href="options.html#opt-services.headscale.enable">services.headscale</link>
+          <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://github.com/lakinduakash/linux-wifi-hotspot">create_ap</link>,
-          a module for creating wifi hotspots using the program
-          linux-wifi-hotspot. Available as
-          <link xlink:href="options.html#opt-services.create_ap.enable">services.create_ap</link>.
+          <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://0xerr0r.github.io/blocky/">blocky</link>,
-          fast and lightweight DNS proxy as ad-blocker for local network
-          with many features.
+          <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://clusterlabs.org/pacemaker/">pacemaker</link>
-          cluster resource manager
+          <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://nifi.apache.org">nifi</link>, an
-          easy to use, powerful, and reliable system to process and
-          distribute data. Available as
-          <link xlink:href="options.html#opt-services.nifi.enable">services.nifi</link>.
+          <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>
@@ -445,6 +619,15 @@
       </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
@@ -473,6 +656,50 @@
       </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>
@@ -566,6 +793,25 @@
       </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>
@@ -768,7 +1014,7 @@
     };
 
     extraConfigFiles = [
-      /run/keys/matrix-synapse/secrets.yaml
+      &quot;/run/keys/matrix-synapse/secrets.yaml&quot;
     ];
   };
 }
@@ -776,7 +1022,9 @@
         <para>
           The secrets in your original config should be migrated into a
           YAML file that is included via
-          <literal>extraConfigFiles</literal>.
+          <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
@@ -791,6 +1039,11 @@
           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>
@@ -1070,6 +1323,16 @@
       </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
@@ -1159,7 +1422,7 @@
               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
+              but this behavior can be overridden by setting the
               <literal>standalone</literal> argument.
             </para>
           </listitem>
@@ -1238,18 +1501,18 @@
       </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
+          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
           <literal>~/.local/share/multimc</literal> to
-          <literal>~/.local/share/polymc</literal>. The main config
-          file’s path has also moved from
+          <literal>~/.local/share/PrismLauncher</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>.
+          <literal>~/.local/share/PrismLauncher/prismlauncher.cfg</literal>.
         </para>
       </listitem>
       <listitem>
@@ -1266,7 +1529,7 @@
           <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>boot.systemd.shutdown.enable</literal>.
+          <literal>systemd.shutdownRamfs.enable</literal>.
         </para>
       </listitem>
       <listitem>
@@ -1334,6 +1597,16 @@
       </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.
@@ -1341,6 +1614,16 @@
       </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
@@ -1495,6 +1778,19 @@
       </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
@@ -1701,6 +1997,43 @@
       </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
@@ -1746,6 +2079,66 @@
       </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>
@@ -1917,13 +2310,6 @@
       </listitem>
       <listitem>
         <para>
-          A new module was added for the Envoy reverse proxy, providing
-          the options <literal>services.envoy.enable</literal> and
-          <literal>services.envoy.settings</literal>.
-        </para>
-      </listitem>
-      <listitem>
-        <para>
           The option <literal>services.duplicati.dataDir</literal> has
           been added to allow changing the location of duplicati’s
           files.
@@ -2119,15 +2505,6 @@
       </listitem>
       <listitem>
         <para>
-          A new module was added for the
-          <link xlink:href="https://starship.rs/">Starship</link> shell
-          prompt, providing the options
-          <literal>programs.starship.enable</literal> and
-          <literal>programs.starship.settings</literal>.
-        </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>
@@ -2149,10 +2526,9 @@
       <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
+          <literal>services.logrotate.paths</literal> and
+          <literal>services.logrotate.extraConfig</literal> will work,
+          but issue deprecation warnings and
           <link linkend="opt-services.logrotate.settings">services.logrotate.settings</link>
           should now be used instead.
         </para>
@@ -2174,6 +2550,14 @@
       </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>
@@ -2272,6 +2656,14 @@
       </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
@@ -2318,6 +2710,14 @@
       </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>
@@ -2326,6 +2726,16 @@
       </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>):
@@ -2351,12 +2761,6 @@
       </listitem>
       <listitem>
         <para>
-          The <literal>programs.nncp</literal> options were added for
-          generating host-global NNCP configuration.
-        </para>
-      </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
@@ -2364,6 +2768,61 @@
           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/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
new file mode 100644
index 000000000000..f7168d5ea17e
--- /dev/null
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -0,0 +1,1841 @@
+<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/30)</title>
+  <para>
+    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.
+  </para>
+  <para>
+    This release is supported until the end of June 2023, handing over
+    to NixOS 23.05.
+  </para>
+  <para>
+    To upgrade to the latest release follow the
+    <link linkend="sec-upgrading">upgrade chapter</link>.
+  </para>
+  <section xml:id="sec-release-22.11-highlights">
+    <title>Highlights</title>
+    <para>
+      In addition to numerous new and upgraded packages, this release
+      includes the following highlights:
+    </para>
+    <itemizedlist>
+      <listitem>
+        <para>
+          Software that uses the <literal>crypt</literal> password
+          hashing API is now using the implementation provided by
+          <link xlink:href="https://github.com/besser82/libxcrypt"><literal>libxcrypt</literal></link>
+          instead of glibc’s, which enables support for more secure
+          algorithms.
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              Support for algorithms that <literal>libxcrypt</literal>
+              <link xlink:href="https://github.com/besser82/libxcrypt/blob/v4.4.28/lib/hashes.conf#L41">does
+              not consider strong</link> are
+              <emphasis role="strong">deprecated</emphasis> as of this
+              release, and will be removed in NixOS 23.05.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              This includes system login passwords. Given this, we
+              <emphasis role="strong">strongly encourage</emphasis> 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.
+            </para>
+            <itemizedlist spacing="compact">
+              <listitem>
+                <para>
+                  When using
+                  <literal>users.users.&lt;name&gt;.hashedPassword</literal>
+                  to configure user passwords, run
+                  <literal>mkpasswd</literal>, and use the yescrypt hash
+                  that is provided as the new value.
+                </para>
+              </listitem>
+              <listitem>
+                <para>
+                  On the other hand, for interactively configured user
+                  passwords, simply re-set the passwords for all users
+                  with <literal>passwd</literal>.
+                </para>
+              </listitem>
+              <listitem>
+                <para>
+                  This release introduces warnings for the use of
+                  deprecated hash algorithms for both methods of
+                  configuring passwords. To make sure you migrated
+                  correctly, run
+                  <literal>nixos-rebuild switch</literal>.
+                </para>
+              </listitem>
+            </itemizedlist>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>aarch64-linux</literal> is now included in the
+          <literal>nixos-22.11</literal> and
+          <literal>nixos-22.11-small</literal> channels. This means that
+          when those channel update, both
+          <literal>x86_64-linux</literal> and
+          <literal>aarch64-linux</literal> will be available in the
+          binary cache.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>aarch64-linux</literal> ISOs are now available on the
+          <link xlink:href="https://nixos.org/download.html">downloads
+          page</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>nsncd</literal> is now available as a replacement of
+          <literal>nscd</literal>.
+        </para>
+        <para>
+          <literal>nscd</literal> is responsible for resolving
+          hostnames, users and more in NixOS and has been a long
+          standing source of bugs, such as sporadic network freezes.
+        </para>
+        <para>
+          More context in this
+          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/135888">issue</link>.
+        </para>
+        <para>
+          Help us test the new implementation by setting
+          <literal>services.nscd.enableNsncd</literal> to
+          <literal>true</literal>.
+        </para>
+        <para>
+          We plan to use <literal>nsncd</literal> by default in NixOS
+          23.05.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Linode cloud images are now supported by importing
+          <literal>${modulesPath}/virtualisation/linode-image.nix</literal>
+          and accessing <literal>system.build.linodeImage</literal> on
+          the output.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>hardware.nvidia</literal> has a new option,
+          <literal>hardware.nvidia.open</literal>, 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
+          <link xlink:href="https://developer.nvidia.com/blog/nvidia-releases-open-source-gpu-kernel-modules/">the
+          release announcement</link> for more information.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>emacs</literal> package now makes use of native
+          compilation which means:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              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;
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Emacs packages from somewhere else, e.g.
+              <literal>package-install</literal>, will perform
+              asynchronously deferred native compilation. If you do not
+              want this, maybe to avoid CPU consumption for compilation,
+              you can use
+              <literal>(setq native-comp-deferred-compilation nil)</literal>
+              to disable it while still benefiting from native
+              compilation for packages from Nixpkgs.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.11-internal">
+    <title>Internal changes</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          Haskell <literal>ghcWithPackages</literal> is now up to 15
+          times faster to evaluate, thanks to changing
+          <literal>lib.closePropagation</literal> from a quadratic to
+          linear complexity. Please see backward incompatibilities notes
+          below.
+          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/194391">https://github.com/NixOS/nixpkgs/pull/194391</link>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          For cross-compilation targets that can also run on the
+          building machine, we now run tests. This, for example, is the
+          case for the <literal>pkgsStatic</literal> and
+          <literal>pkgsLLVM</literal> package sets or i686 packages on
+          <literal>x86_64</literal> machines.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          To simplify cross-compilation in NixOS, this release
+          introduces the <literal>nixpkgs.hostPlatform</literal> and
+          <literal>nixpkgs.buildPlatform</literal> options. 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>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.11-version-updates">
+    <title>Notable version updates</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          Nix has been upgraded from v2.8.1 to v2.11.0. For more
+          information, please see the release notes for
+          <link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.9.html">2.9</link>,
+          <link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.10.html">2.10</link>
+          and
+          <link xlink:href="https://nixos.org/manual/nix/stable/release-notes/rl-2.11.html">2.11</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          OpenSSL now defaults to OpenSSL 3, updated from 1.1.1.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          GNOME has been upgraded to version 43. Please see the
+          <link xlink:href="https://release.gnome.org/43/">release
+          notes</link> for details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          KDE Plasma has been upgraded from v5.24 to v5.26. Please see
+          the release notes for
+          <link xlink:href="https://kde.org/announcements/plasma/5/5.25.0/">v5.25</link>
+          and
+          <link xlink:href="https://kde.org/announcements/plasma/5/5.26.0/">v5.26</link>
+          for more details on the included changes.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          PHP now defaults to PHP 8.1, updated from 8.0.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Perl has been updated to 5.36, and its core module
+          <literal>HTTP::Tiny</literal> was patched to verify SSL/TLS
+          certificates by default.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Python now defaults to 3.10, updated from 3.9.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.11-incompatibilities">
+    <title>Backward Incompatibilities</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          Nixpkgs now requires Nix 2.3 or newer.
+        </para>
+      </listitem>
+      <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
+          takes the kernel / syscall interface into account.
+          <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 hyphen arguments was dropped.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>i18n.supportedLocales</literal> is now only generated
+          with the locales set in <literal>i18n.defaultLocale</literal>
+          and <literal>i18n.extraLocaleSettings</literal>.
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              This reduces the final system closure size by up to 200MB.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              If you require all locales installed, set the option to
+              <literal>[ &quot;all&quot; ]</literal>.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          Deprecated settings <literal>logrotate.paths</literal> and
+          <literal>logrotate.extraConfig</literal> have been removed.
+          Please convert any uses to
+          <link linkend="opt-services.logrotate.settings">services.logrotate.settings</link>
+          instead.
+        </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>
+          The <literal>fetchgit</literal> fetcher supports sparse
+          checkouts via the <literal>sparseCheckout</literal> option.
+          This used to accept a multi-line string with
+          directories/patterns to check out, but now requires a list of
+          strings.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>openssh</literal> was updated to version 9.1,
+          disabling the generation of DSA keys when using
+          <literal>ssh-keygen -A</literal> as they are insecure. Also,
+          <literal>SetEnv</literal> directives in
+          <literal>ssh_config</literal> and
+          <literal>sshd_config</literal> are now first-match-wins.
+        </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>
+          The <literal>p4</literal> 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
+          <literal>p4d</literal>, <literal>p4broker</literal>, and
+          <literal>p4p</literal>. To install the Helix Core Server
+          binaries, use the <literal>p4d</literal> package instead.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The OpenSSL extension for the PHP interpreter used by
+          Nextcloud is built against OpenSSL 1.1 if
+          <xref linkend="opt-system.stateVersion" /> is below
+          <literal>22.11</literal>. This is to make sure that people
+          using
+          <link xlink:href="https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html">server-side
+          encryption</link> don’t lose access to their files.
+        </para>
+        <para>
+          In any other case, it’s safe to use OpenSSL 3 for PHP’s
+          OpenSSL extension. This can be done by setting
+          <xref linkend="opt-services.nextcloud.enableBrokenCiphersForSSE" />
+          to <literal>false</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>coq</literal> package and versioned variants
+          starting at <literal>coq_8_14</literal> no longer include
+          CoqIDE, which is now available through
+          <literal>coqPackages.coqide</literal>. It is still possible to
+          get CoqIDE as part of the <literal>coq</literal> package by
+          overriding the <literal>buildIde</literal> argument of the
+          derivation.
+        </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>
+          The ipfs package and module were renamed to kubo. The kubo
+          module now uses an RFC42-style <literal>settings</literal>
+          option instead of <literal>extraConfig</literal> and the
+          <literal>gatewayAddress</literal>,
+          <literal>apiAddress</literal> and
+          <literal>swarmAddress</literal> options were renamed. Using
+          the old names will print a warning but still work.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>pkgs.cosign</literal> does not provide the
+          <literal>cosigned</literal> binary anymore. The
+          <literal>sget</literal> binary has been moved into its own
+          package.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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
+          <literal>emacs-gtk</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>kanidm</literal> has been updated to 1.1.0-alpha.10
+          and now requires a TLS certificate and key. It will always
+          start <literal>https</literal> and-–-if enabled-–-an LDAPS
+          server and no HTTP and LDAP server 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>
+          ppd files in <literal>pkgs.cups-drv-rastertosag-gdi</literal>
+          are now gzipped. If you refer to such a ppd file with its path
+          (e.g. via
+          <link xlink:href="options.html#opt-hardware.printers.ensurePrinters">hardware.printers.ensurePrinters</link>)
+          you will need to append <literal>.gz</literal> to the path.
+        </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>
+          dd-agent package removed along with the
+          <literal>services.dd-agent</literal> module, due to the
+          project being deprecated in favor of
+          <literal>datadog-agent</literal>, which is available via the
+          <literal>services.datadog-agent</literal> module.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>teleport</literal> has been upgraded to major version
+          10. Please see upstream
+          <link xlink:href="https://goteleport.com/docs/ver/10.0/management/operations/upgrading/">upgrade
+          instructions</link> and
+          <link xlink:href="https://goteleport.com/docs/ver/10.0/changelog/#1000">release
+          notes</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>lib.closePropagation</literal> now needs that all
+          gathered sets have an <literal>outPath</literal> attribute.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          lemmy module option
+          <literal>services.lemmy.settings.database.createLocally</literal>
+          moved to
+          <literal>services.lemmy.database.createLocally</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          virtlyst package and <literal>services.virtlyst</literal>
+          module removed, due to lack of maintainers.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>nix.checkConfig</literal> option now fully
+          disables the config check. The new
+          <literal>nix.checkAllErrors</literal> option behaves like
+          <literal>nix.checkConfig</literal> previously did.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>generateOptparseApplicativeCompletions</literal> and
+          <literal>generateOptparseApplicativeCompletion</literal> from
+          <literal>haskell.lib.compose</literal> (and
+          <literal>haskell.lib</literal>) have been deprecated in favor
+          of <literal>generateOptparseApplicativeCompletions</literal>
+          (plural!) as provided by the haskell package sets (so
+          <literal>haskellPackages.generateOptparseApplicativeCompletions</literal>
+          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
+          <literal>self.generateOptparseApplicativeCompletions</literal>
+          if you are overriding a haskell package set. The old functions
+          are retained for backwards compatibility, but yield are
+          warning.
+        </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>trace</literal> binary from
+          <literal>perf-linux</literal> package has been removed, due to
+          being a duplicate of the <literal>perf</literal> binary.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>aws</literal> package has been removed due to
+          being abandoned by the upstream. It is recommended to use
+          <literal>awscli</literal> or <literal>awscli2</literal>
+          instead.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The
+          <link xlink:href="https://ce-programming.github.io/CEmu">CEmu
+          TI-84 Plus CE emulator</link> package has been renamed to
+          <literal>cemu-ti</literal>. The
+          <link xlink:href="https://cemu.info">Cemu Wii U
+          emulator</link> is now packaged as <literal>cemu</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>systemd-networkd</literal> v250 deprecated, renamed,
+          and moved some sections and settings which leads to the
+          following breaking module changes:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              <literal>systemd.network.networks.&lt;name&gt;.dhcpV6PrefixDelegationConfig</literal>
+              is renamed to
+              <literal>systemd.network.networks.&lt;name&gt;.dhcpPrefixDelegationConfig</literal>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>systemd.network.networks.&lt;name&gt;.dhcpV6Config</literal>
+              no longer accepts the
+              <literal>ForceDHCPv6PDOtherInformation=</literal> setting.
+              Please use the <literal>WithoutRA=</literal> and
+              <literal>UseDelegatedPrefix=</literal> settings in your
+              <literal>systemd.network.networks.&lt;name&gt;.dhcpV6Config</literal>
+              and the <literal>DHCPv6Client=</literal> setting in your
+              <literal>systemd.network.networks.&lt;name&gt;.ipv6AcceptRAConfig</literal>
+              to control when the DHCPv6 client is started and how the
+              delegated prefixes are handled by the DHCPv6 client.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>systemd.network.networks.&lt;name&gt;.networkConfig</literal>
+              no longer accepts the <literal>IPv6Token=</literal>
+              setting. Use the <literal>Token=</literal> setting in your
+              <literal>systemd.network.networks.&lt;name&gt;.ipv6AcceptRAConfig</literal>
+              instead. The
+              <literal>systemd.network.networks.&lt;name&gt;.ipv6Prefixes.*.ipv6PrefixConfig</literal>
+              now also accepts the <literal>Token=</literal> setting.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>arangodb</literal> 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
+          <literal>arangodb</literal> is now built for the
+          <literal>haswell</literal> architecture. If you wish to build
+          for a different architecture, you may override the
+          <literal>targetArchitecture</literal> argument with a value
+          from
+          <link xlink:href="https://github.com/arangodb/arangodb/blob/207ec6937e41a46e10aea34953879341f0606841/cmake/OptimizeForArchitecture.cmake#L594">this
+          list supported upstream</link>. Some architecture specific
+          optimizations are also conditionally enabled. You may alter
+          this behavior by overriding the
+          <literal>asmOptimizations</literal> parameter. You may also
+          add additional architecture support by adding more
+          <literal>-DHAS_XYZ</literal> flags to
+          <literal>cmakeFlags</literal> via
+          <literal>overrideAttrs</literal>.
+        </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>
+          The <literal>paperless</literal> module now defaults
+          <literal>PAPERLESS_TIME_ZONE</literal> to your configured
+          system timezone.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The top-level <literal>termonad-with-packages</literal> alias
+          for <literal>termonad</literal> has been removed.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Linux 4.9 has been removed because it will reach its end of
+          life within the lifespan of 22.11.
+        </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>
+          Neovim can not be configured with plug anymore (still works
+          for vim).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>adguardhome</literal> module no longer uses
+          <literal>host</literal> and <literal>port</literal> options,
+          use <literal>settings.bind_host</literal> and
+          <literal>settings.bind_port</literal> instead.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The default <literal>kops</literal> version is now 1.25.1 and
+          support for 1.22 and older has been dropped.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>zrepl</literal> package has been updated from
+          0.5.0 to 0.6.0. See the
+          <link xlink:href="https://zrepl.github.io/changelog.html">changelog</link>
+          for details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>k3s</literal> no longer supports Docker as runtime
+          due to upstream dropping support.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>cassandra_2_1</literal> and
+          <literal>cassandra_2_2</literal> have been removed. Please
+          update to <literal>cassandra_3_11</literal> or
+          <literal>cassandra_3_0</literal>. See the
+          <link xlink:href="https://github.com/apache/cassandra/blob/cassandra-3.11.14/NEWS.txt">changelog</link>
+          for more information about the upgrade process.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>mysql57</literal> has been removed. Please update to
+          <literal>mysql80</literal> or <literal>mariadb</literal>. See
+          the
+          <link xlink:href="https://mariadb.com/kb/en/upgrading-from-mysql-to-mariadb/">upgrade
+          guide</link> for more information.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Consequently, <literal>cqrlog</literal> and
+          <literal>amorok</literal> now use <literal>mariadb</literal>
+          instead of <literal>mysql57</literal> for their embedded
+          databases. Running <literal>mysql_upgrade</literal> may be
+          neccesary.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>k3s</literal> supports <literal>clusterInit</literal>
+          option, and it is enabled by default, for servers.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>percona-server56</literal> has been removed. Please
+          migrate to <literal>mysql</literal> or
+          <literal>mariadb</literal> if possible.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>obs-studio</literal> hase been updated to version 28.
+          If you have packaged custom plugins, check if they are
+          compatible. <literal>obs-websocket</literal> has been
+          integrated into <literal>obs-studio</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>signald</literal> has been bumped to
+          <literal>0.23.0</literal>. For the upgrade, a migration
+          process is necessary. It can be done by running a command like
+          this before starting <literal>signald.service</literal>:
+        </para>
+        <programlisting>
+signald -d /var/lib/signald/db \
+  --database sqlite:/var/lib/signald/db \
+  --migrate-data
+</programlisting>
+        <para>
+          For further information, please read the upstream changelogs.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>stylua</literal> no longer accepts
+          <literal>lua52Support</literal> and
+          <literal>luauSupport</literal> overrides. Use
+          <literal>features</literal> instead, which defaults to
+          <literal>[ &quot;lua54&quot; &quot;luau&quot; ]</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>ocamlPackages.ocaml_extlib</literal> has been renamed
+          to <literal>ocamlPackages.extlib</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>pkgs.fetchNextcloudApp</literal> 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
+          <literal>name</literal> and <literal>version</literal> are no
+          longer accepted.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The Syncthing service now only allows absolute paths—starting
+          with <literal>/</literal> or <literal>~/</literal>—for
+          <literal>services.syncthing.folders.&lt;name&gt;.path</literal>.
+          In a future release other paths will be allowed again and
+          interpreted relative to
+          <literal>services.syncthing.dataDir</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>services.github-runner</literal> and
+          <literal>services.github-runners.&lt;name&gt;</literal> gained
+          the option <literal>serviceOverrides</literal> which allows
+          overriding the systemd <literal>serviceConfig</literal>. If
+          you have been overriding the systemd service configuration
+          (i.e., by defining
+          <literal>systemd.services.github-runner.serviceConfig</literal>),
+          you have to use the <literal>serviceOverrides</literal> option
+          now. Example:
+        </para>
+        <programlisting>
+services.github-runner.serviceOverrides.SupplementaryGroups = [
+  &quot;docker&quot;
+];
+</programlisting>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.11-notable-changes">
+    <title>Other Notable Changes</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          PHP is now built in <literal>NTS</literal> (Non-Thread Safe)
+          mode by default.
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              For Apache and <literal>mod_php</literal> usage, we enable
+              <literal>ZTS</literal> (Zend Thread Safe) mode. This has
+              been a common practice for a long time in other
+              distributions.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>firefox</literal>, <literal>thunderbird</literal> and
+          <literal>librewolf</literal> now come with Wayland support by
+          default. The <literal>firefox-wayland</literal>,
+          <literal>firefox-esr-wayland</literal>,
+          <literal>thunderbird-wayland</literal> and
+          <literal>librewolf-wayland</literal> attributes are obsolete
+          and have been aliased to their generic attribute.
+        </para>
+      </listitem>
+      <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>
+          Configuring multiple GitHub runners is now possible through
+          <literal>services.github-runners.&lt;name&gt;</literal>. The
+          options under <literal>services.github-runner</literal>
+          remain, to configure a single runner.
+        </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 to provide hardware support 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>
+          ZFS module will no longer allow hibernation by default.
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              This is a safety measure to prevent data loss cases like
+              the ones described at
+              <link xlink:href="https://github.com/openzfs/zfs/issues/260">OpenZFS/260</link>
+              and
+              <link xlink:href="https://github.com/openzfs/zfs/issues/12842">OpenZFS/12842</link>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Use the <literal>boot.zfs.allowHibernation</literal>
+              option to configure this behaviour.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          Mastodon now automatically removes remote media attachments
+          older than 30 days. This is configurable through
+          <literal>services.mastodon.mediaAutoRemove</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 upstream’s
+          <link xlink:href="https://neo4j.com/docs/upgrade-migration-guide/current/">migration
+          guide</link> for information on how to migrate your instance.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>networking.wireguard</literal> module now can set
+          the mtu on interfaces and tag its packets with an fwmark.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The option <literal>overrideStrategy</literal> was added to
+          the different systemd unit options
+          (<literal>systemd.services.&lt;name&gt;</literal>,
+          <literal>systemd.sockets.&lt;name&gt;</literal>, …) to allow
+          enforcing the creation of a dropin file, rather than the main
+          unit file, by setting it to <literal>asDropin</literal>. 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
+          <literal>systemd.packages</literal>. See the fix proposed in
+          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/135557#issuecomment-1295392470">NixOS’s
+          systemd abstraction doesn’t work with systemd template
+          units</link> for an example.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>polymc</literal> package has been removed due to
+          a rogue maintainer. It has been replaced by
+          <literal>prismlauncher</literal>, a fork by the rest of the
+          maintainers. For more details, see
+          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/196624">the
+          PR that made this change</link> and
+          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/196460">the
+          issue detailing the vulnerability</link>. Users with existing
+          installations should rename
+          <literal>~/.local/share/polymc</literal> to
+          <literal>~/.local/share/PrismLauncher</literal>. The main
+          config file’s path has also moved from
+          <literal>~/.local/share/polymc/polymc.cfg</literal> to
+          <literal>~/.local/share/PrismLauncher/prismlauncher.cfg</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>bloat</literal> package has been updated from
+          unstable-2022-03-31 to unstable-2022-10-25, which brings a
+          breaking change. See
+          <link xlink:href="https://git.freesoftwareextremist.com/bloat/commit/?id=887ed241d64ba5db3fd3d87194fb5595e5ad7d73">this
+          upstream commit message</link> for details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Synapse’s systemd unit has been hardened.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The module <literal>services.grafana</literal> was refactored
+          to be compliant with
+          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
+          0042</link>. To be precise, this means that the following
+          things have changed:
+        </para>
+        <itemizedlist>
+          <listitem>
+            <para>
+              The newly introduced option
+              <xref linkend="opt-services.grafana.settings" /> is an
+              attribute-set that will be converted into Grafana’s INI
+              format. This means that the configuration from
+              <link xlink:href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/">Grafana’s
+              configuration reference</link> can be directly written as
+              attribute-set in Nix within this option.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The option
+              <literal>services.grafana.extraOptions</literal> has been
+              removed. This option was an association of environment
+              variables for Grafana. If you had an expression like
+            </para>
+            <programlisting language="bash">
+{
+  services.grafana.extraOptions.SECURITY_ADMIN_USER = &quot;foobar&quot;;
+}
+</programlisting>
+            <para>
+              your Grafana instance was running with
+              <literal>GF_SECURITY_ADMIN_USER=foobar</literal> in its
+              environment.
+            </para>
+            <para>
+              For the migration, it is recommended to turn it into the
+              INI format, i.e. to declare
+            </para>
+            <programlisting language="bash">
+{
+  services.grafana.settings.security.admin_user = &quot;foobar&quot;;
+}
+</programlisting>
+            <para>
+              instead.
+            </para>
+            <para>
+              The keys in
+              <literal>services.grafana.extraOptions</literal> have the
+              format
+              <literal>&lt;INI section name&gt;_&lt;Key Name&gt;</literal>.
+              Further details are outlined in the
+              <link xlink:href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#override-configuration-with-environment-variables">configuration
+              reference</link>.
+            </para>
+            <para>
+              Alternatively you can also set all your values from
+              <literal>extraOptions</literal> to
+              <literal>systemd.services.grafana.environment</literal>,
+              make sure you don’t forget to add the
+              <literal>GF_</literal> prefix though!
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Previously, the options
+              <link linkend="opt-services.grafana.provision.datasources">services.grafana.provision.datasources</link>
+              and
+              <link linkend="opt-services.grafana.provision.dashboards">services.grafana.provision.dashboards</link>
+              expected lists of datasources or dashboards for the
+              <link xlink:href="https://grafana.com/docs/grafana/latest/administration/provisioning/">declarative
+              provisioning</link>.
+            </para>
+            <para>
+              To declare lists of
+            </para>
+            <itemizedlist spacing="compact">
+              <listitem>
+                <para>
+                  <emphasis role="strong">datasources</emphasis>, please
+                  rename your declarations to
+                  <link linkend="opt-services.grafana.provision.datasources.settings.datasources">services.grafana.provision.datasources.settings.datasources</link>.
+                </para>
+              </listitem>
+              <listitem>
+                <para>
+                  <emphasis role="strong">dashboards</emphasis>, please
+                  rename your declarations to
+                  <link linkend="opt-services.grafana.provision.dashboards.settings.providers">services.grafana.provision.dashboards.settings.providers</link>.
+                </para>
+              </listitem>
+            </itemizedlist>
+            <para>
+              This change was made to support more features for that:
+            </para>
+            <itemizedlist>
+              <listitem>
+                <para>
+                  It’s possible to declare the
+                  <literal>apiVersion</literal> of your dashboards and
+                  datasources by
+                  <link linkend="opt-services.grafana.provision.datasources.settings.apiVersion">services.grafana.provision.datasources.settings.apiVersion</link>
+                  (or
+                  <link linkend="opt-services.grafana.provision.dashboards.settings.apiVersion">services.grafana.provision.dashboards.settings.apiVersion</link>).
+                </para>
+              </listitem>
+              <listitem>
+                <para>
+                  Instead of declaring datasources and dashboards in
+                  pure Nix, it’s also possible to specify configuration
+                  files (or directories) with YAML instead using
+                  <link linkend="opt-services.grafana.provision.datasources.path">services.grafana.provision.datasources.path</link>
+                  (or
+                  <link linkend="opt-services.grafana.provision.dashboards.path">services.grafana.provision.dashboards.path</link>.
+                  This is useful when having provisioning files from
+                  non-NixOS Grafana instances that you also want to
+                  deploy to NixOS.
+                </para>
+                <para>
+                  <emphasis role="strong">Note:</emphasis> secrets from
+                  these files will be leaked into the store unless you
+                  use a
+                  <link xlink:href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider"><emphasis role="strong">file</emphasis>-provider
+                  or env-var</link> for secrets!
+                </para>
+              </listitem>
+              <listitem>
+                <para>
+                  <link linkend="opt-services.grafana.provision.notifiers">services.grafana.provision.notifiers</link>
+                  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
+                  <literal>services.grafana.provision.alerting.contactPoints</literal>
+                  instead.
+                </para>
+              </listitem>
+            </itemizedlist>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>services.grafana.provision.alerting</literal>
+          option was added. It includes suboptions for every
+          alerting-related objects (with the exception of
+          <literal>notifiers</literal>), which means it’s now possible
+          to configure modern Grafana alerting declaratively.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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>
+          The <literal>diamond</literal> package has been update from
+          0.8.36 to 2.0.15. See the
+          <link xlink:href="https://github.com/bbuchfink/diamond/releases">upstream
+          release notes</link> for more details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>guake</literal> package has been updated from
+          3.6.3 to 3.9.0, see the
+          <link xlink:href="https://github.com/Guake/guake/releases">changelog</link>
+          for more details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>netlify-cli</literal> package has been updated
+          from 6.13.2 to 12.2.4, see the
+          <link xlink:href="https://github.com/netlify/cli/releases">changelog</link>
+          for more details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>dockerTools.buildImage</literal>’s
+          <literal>contents</literal> parameter has been deprecated 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>
+          The <literal>proxmox.qemuConf.bios</literal> option was added,
+          it corresponds to <literal>Hardware-&gt;BIOS</literal> field
+          in Proxmox web interface. Use
+          <literal>&quot;ovmf&quot;</literal> value to build UEFI image,
+          default value remains <literal>&quot;bios&quot;</literal>. New
+          option <literal>proxmox.partitionTableType</literal> defaults
+          to either <literal>&quot;legacy&quot;</literal> or
+          <literal>&quot;efi&quot;</literal>, depending on the
+          <literal>bios</literal> value. Setting
+          <literal>partitionTableType</literal> to
+          <literal>&quot;hybrid&quot;</literal> results in an image,
+          which supports both methods
+          (<literal>&quot;bios&quot;</literal> and
+          <literal>&quot;ovmf&quot;</literal>), thereby remaining
+          bootable after change to Proxmox
+          <literal>Hardware-&gt;BIOS</literal> field.
+        </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>
+          Option descriptions, examples, and defaults writing in DocBook
+          are now deprecated. Using CommonMark is preferred and will
+          become the default in a future release.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The
+          <literal>documentation.nixos.options.allowDocBook</literal>
+          option was added to ease the transition to CommonMark option
+          documentation. Setting this option to <literal>false</literal>
+          causes an error for every option included in the manual that
+          uses DocBook documentation; it defaults to
+          <literal>true</literal> to preserve the previous behavior and
+          will be removed once the transition to CommonMark is complete.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>protonup</literal> has been aliased to and replaced
+          by <literal>protonup-ng</literal> due to upstream not
+          maintaining it.
+        </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>
+          Nextcloud has been updated to version
+          <emphasis role="strong">25</emphasis>. Additionally the
+          following things have changed for Nextcloud in NixOS:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              For Nextcloud <emphasis role="strong">&gt;=24</emphasis>,
+              the default PHP version is 8.1.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              Nextcloud <emphasis role="strong">23</emphasis> has been
+              removed since it will reach its
+              <link xlink:href="https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule/d76576a12a626d53305d480a6065b57cab705d3d">end
+              of life in December 2022</link>.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              If <literal>system.stateVersion</literal> is
+              <emphasis role="strong">&gt;=22.11</emphasis>, Nextcloud
+              25 will be installed by default. For older versions,
+              Nextcloud 24 will be installed.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              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
+              <emphasis role="strong">23</emphasis> to
+              <emphasis role="strong">25</emphasis> is only possible
+              when upgrading to <emphasis role="strong">24</emphasis>
+              first.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          systemd-oomd is enabled by default. Depending on which systemd
+          units have <literal>ManagedOOMSwap=kill</literal> or
+          <literal>ManagedOOMMemoryPressure=kill</literal>, 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
+          <link xlink:href="options.html#opt-systemd.oomd.enableRootSlice">systemd.oomd.enableRootSlice</link>,
+          <link xlink:href="options.html#opt-systemd.oomd.enableSystemSlice">systemd.oomd.enableSystemSlice</link>,
+          and
+          <link xlink:href="options.html#opt-systemd.oomd.enableUserServices">systemd.oomd.enableUserServices</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>tt-rss</literal> service performs two database
+          migrations when you first use its web UI after upgrade.
+          Consider backing up its database before updating.
+        </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>
+          The Wordpress module now has support for installing language
+          packs through a new option,
+          <literal>services.wordpress.sites.&lt;site&gt;.languages</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The default package for
+          <literal>services.mullvad-vpn.package</literal> was changed to
+          <literal>pkgs.mullvad</literal>, allowing cross-platform usage
+          of Mullvad. <literal>pkgs.mullvad</literal> only contains the
+          Mullvad CLI tool, so users who rely on the Mullvad GUI will
+          want to change it back to <literal>pkgs.mullvad-vpn</literal>,
+          or add <literal>pkgs.mullvad-vpn</literal> to their
+          environment.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          PowerDNS has been updated from v4.6.2 to v4.7.2. Please be
+          sure to review the
+          <link xlink:href="https://doc.powerdns.com/authoritative/upgrading.html#to-4-7-0-or-master">Upgrade
+          Notes</link> 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.
+        </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 may be removed
+          in a future release.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          There is a new module for <literal>xfconf</literal> (the Xfce
+          configuration storage system), which has a dbus service.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The Mastodon package has been upgraded to v4.0.0. See the
+          <link xlink:href="https://github.com/mastodon/mastodon/releases/tag/v4.0.0">v4.0.0
+          release notes</link> for a list of changes. On standard
+          setups, no manual migration steps are required. Nevertheless,
+          a database backup is recommended.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>nomad</literal> package now defaults to v1.3,
+          which no longer has a downgrade path to v1.2 or older.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>nodePackages</literal> package set now defaults
+          to the LTS release in the <literal>nodejs</literal> package
+          again, instead of being pinned to
+          <literal>nodejs-14_x</literal>. Several updates to node2nix
+          have been made for compatibility with newer Node.js and npm
+          versions and a new <literal>postRebuild</literal> hook has
+          been added for packages to perform extra build steps before
+          the npm install step prunes dev dependencies.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>boot.kernel.sysctl</literal> is defined as a
+          freeformType and adds a custom merge option for
+          <literal>net.core.rmem_max</literal> (taking the highest value
+          defined to avoid conflicts between 2 services trying to set
+          that value).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>mame</literal> package does not ship with its
+          tools anymore in the default output. They were moved to a
+          separate <literal>tools</literal> output instead. For
+          convenience, <literal>mame-tools</literal> package was added
+          for those who want to use it.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          A NixOS module for Firefox has been added which allows
+          preferences and
+          <link xlink:href="https://github.com/mozilla/policy-templates/blob/master/README.md">policies</link>
+          to be set. This also allows extensions to be installed via the
+          <literal>ExtensionSettings</literal> policy. The new options
+          are under <literal>programs.firefox</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The option
+          <literal>services.picom.experimentalBackends</literal> was
+          removed since it is now the default and the option will cause
+          <literal>picom</literal> to quit instead.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>haskellPackages.callHackage</literal> is not always
+          invalidated if <literal>all-cabal-hashes</literal> changes,
+          leading to less rebuilds of haskell dependencies.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>haskellPackages.callHackage</literal> and
+          <literal>haskellPackages.callCabal2nix</literal> (and related
+          functions) no longer keep a reference to the
+          <literal>cabal2nix</literal> call used to generate them. As a
+          result, they will be garbage collected more often.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-22.11-new-services">
+    <title>New Services</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <link xlink:href="https://git.sr.ht/~migadu/alps">alps</link>,
+          a simple and extensible webmail. Available as
+          <link linkend="opt-services.alps.enable">services.alps</link>.
+        </para>
+      </listitem>
+      <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://www.ausweisapp.bund.de/">AusweisApp2</link>,
+          the authentication software for the German ID card. Available
+          as
+          <link linkend="opt-programs.ausweisapp.enable">programs.ausweisapp</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/maxbrunet/automatic-timezoned">automatic-timezoned</link>.
+          a Linux daemon to automatically update the system timezone
+          based on location. Available as
+          <link linkend="opt-services.automatic-timezoned.enable">services.automatic-timezoned</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://www.dolibarr.org/">Dolibarr</link>,
+          an enterprise resource planning and customer relationship
+          manager. Enable using
+          <link linkend="opt-services.dolibarr.enable">services.dolibarr</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://github.com/shizunge/endlessh-go">endlessh-go</link>,
+          an SSH tarpit that exposes Prometheus metrics. Available as
+          <link linkend="opt-services.endlessh-go.enable">services.endlessh-go</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/skeeto/endlessh">endlessh</link>,
+          an SSH tarpit. Available as
+          <link linkend="opt-services.endlessh.enable">services.endlessh</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://evcc.io">EVCC</link> 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
+          <link linkend="opt-services.evcc.enable">services.evcc</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://freshrss.org/">FreshRSS</link>, a
+          free, self-hostable RSS feed aggregator. Available as
+          <link linkend="opt-services.freshrss.enable">services.freshrss</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://garagehq.deuxfleurs.fr/">Garage</link>,
+          a simple object storage server for geodistributed deployments,
+          alternative to MinIO. Available as
+          <link linkend="opt-services.garage.enable">services.garage</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/L11R/go-autoconfig">go-autoconfig</link>,
+          IMAP/SMTP autodiscover server. Available as
+          <link linkend="opt-services.go-autoconfig.enable">services.go-autoconfig</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://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/prymitive/karma">karma</link>,
+          an alert dashboard for Prometheus Alertmanager. Available as
+          <link xlink:href="options.html#opt-services.karma.enable">services.karma</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://github.com/prymitive/kthxbye">kthxbye</link>,
+          an alert acknowledgement management daemon for Prometheus
+          Alertmanager. Available as
+          <link xlink:href="options.html#opt-services.kthxbye.enable">services.kthxbye</link>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://languagetool.org/">languagetool</link>,
+          a multilingual grammar, style, and spell checker. Available as
+          <link xlink:href="options.html#opt-services.languagetool.enable">services.languagetool</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://listmonk.app">Listmonk</link>, a
+          self-hosted newsletter manager. Enable using
+          <link xlink:href="options.html#opt-services.listmonk.enable">services.listmonk</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://mepo.milesalan.com">Mepo</link>, a
+          fast, simple, hackable OSM map viewer for mobile and desktop
+          Linux. Available as
+          <link linkend="opt-programs.mepo.enable">programs.mepo.enable</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://troglobit.com/projects/merecat/">merecat</link>,
+          a small and easy HTTP server based on thttpd. Available as
+          <link linkend="opt-services.merecat.enable">services.merecat</link>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://netbird.io">netbird</link>, a zero
+          configuration VPN. Available as
+          <link xlink:href="options.html#opt-services.netbird.enable">services.netbird</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://ntfy.sh">ntfy.sh</link>, a push
+          notification service. Available as
+          <link linkend="opt-services.ntfy-sh.enable">services.ntfy-sh</link>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://gitlab.com/CalcProgrammer1/OpenRGB/-/tree/master">OpenRGB</link>,
+          a FOSS tool for controlling RGB lighting. Available as
+          <link xlink:href="options.html#opt-services.hardware.openrgb.enable">services.hardware.openrgb.enable</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://www.getoutline.com/">Outline</link>,
+          a wiki and knowledge base similar to Notion. Available as
+          <link linkend="opt-services.outline.enable">services.outline</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>
+      <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://github.com/edneville/please">Please</link>,
+          a Sudo clone written in Rust. Available as
+          <link linkend="opt-security.please.enable">security.please</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/prometheus-community/ipmi_exporter">Prometheus
+          IPMI exporter</link>, an IPMI exporter for Prometheus.
+          Available as
+          <link linkend="opt-services.prometheus.exporters.ipmi.enable">services.prometheus.exporters.ipmi</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/messagebird/sachet/">Sachet</link>,
+          an SMS alerting tool for the Prometheus Alertmanager.
+          Available as
+          <link linkend="opt-services.prometheus.sachet.enable">services.prometheus.sachet</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://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://tandoor.dev">Tandoor Recipes</link>,
+          a self-hosted multi-tenant recipe collection. Available as
+          <link xlink:href="options.html#opt-services.tandoor-recipes.enable">services.tandoor-recipes</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="http://www.litech.org/tayga/">TAYGA</link>,
+          an out-of-kernel stateless NAT64 implementation. Available as
+          <link linkend="opt-services.tayga.enable">services.tayga</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/tmate-io/tmate-ssh-server">tmate-ssh-server</link>,
+          server side part of
+          <link xlink:href="https://tmate.io/">tmate</link>. Available
+          as
+          <link linkend="opt-services.tmate-ssh-server.enable">services.tmate-ssh-server</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://uptime.kuma.pet/">Uptime
+          Kuma</link>, a fancy self-hosted monitoring tool. Available as
+          <link linkend="opt-services.uptime-kuma.enable">services.uptime-kuma</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://writefreely.org">WriteFreely</link>,
+          a simple blogging platform with ActivityPub support. Available
+          as
+          <link xlink:href="options.html#opt-services.writefreely.enable">services.writefreely</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/XTLS/Xray-core">xray</link>,
+          a fully compatible v2ray-core replacement. Features XTLS,
+          which when enabled on server and client, brings UDP FullCone
+          NAT to proxy setups. Available as
+          <link xlink:href="options.html#opt-services.xray.enable">services.xray</link>.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+</section>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
new file mode 100644
index 000000000000..b410a660c551
--- /dev/null
+++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
@@ -0,0 +1,380 @@
+<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-release-23.05">
+  <title>Release 23.05 (“Stoat”, 2023.05/??)</title>
+  <para>
+    Support is planned until the end of December 2023, handing over to
+    23.11.
+  </para>
+  <section xml:id="sec-release-23.05-highlights">
+    <title>Highlights</title>
+    <para>
+      In addition to numerous new and upgraded packages, this release
+      has the following highlights:
+    </para>
+    <itemizedlist spacing="compact">
+      <listitem>
+        <para>
+          Cinnamon has been updated to 5.6, see
+          <link xlink:href="https://github.com/NixOS/nixpkgs/pull/201328#issue-1449910204">the
+          pull request</link> for what is changed.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-23.05-new-services">
+    <title>New Services</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/akinomyoga/ble.sh">blesh</link>,
+          a line editor written in pure bash. Available as
+          <link linkend="opt-programs.bash.blesh.enable">programs.bash.blesh</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/junegunn/fzf">fzf</link>,
+          a command line fuzzyfinder. Available as
+          <link linkend="opt-programs.fzf.fuzzyCompletion">programs.fzf</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/ellie/atuin">atuin</link>,
+          a sync server for shell history. Available as
+          <link linkend="opt-services.atuin.enable">services.atuin</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://gitlab.com/kop316/mmsd">mmsd</link>,
+          a lower level daemon that transmits and recieves MMSes.
+          Available as
+          <link linkend="opt-services.mmsd.enable">services.mmsd</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://v2raya.org">v2rayA</link>, a Linux
+          web GUI client of Project V which supports V2Ray, Xray, SS,
+          SSR, Trojan and Pingtunnel. Available as
+          <link xlink:href="options.html#opt-services.v2raya.enable">services.v2raya</link>.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-23.05-incompatibilities">
+    <title>Backward Incompatibilities</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <literal>carnix</literal> and <literal>cratesIO</literal> has
+          been removed due to being unmaintained, use alternatives such
+          as
+          <link xlink:href="https://github.com/nix-community/naersk">naersk</link>
+          and
+          <link xlink:href="https://github.com/kolloch/crate2nix">crate2nix</link>
+          instead.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>borgbackup</literal> module now has an option for
+          inhibiting system sleep while backups are running, defaulting
+          to off (not inhibiting sleep), available as
+          <link linkend="opt-services.borgbackup.jobs._name_.inhibitsSleep"><literal>services.borgbackup.jobs.&lt;name&gt;.inhibitsSleep</literal></link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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
+          <literal>/etc/ec2-metadata</literal> should now have an
+          <literal>after</literal> dependency on
+          <literal>fetch-ec2-metadata.service</literal>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>minio</literal> removed support for its legacy
+          filesystem backend in
+          <link xlink:href="https://github.com/minio/minio/releases/tag/RELEASE.2022-10-29T06-21-33Z">RELEASE.2022-10-29T06-21-33Z</link>.
+          This means if your storage was created with the old format,
+          minio will no longer start. Unfortunately minio doesn’t
+          provide a an automatic migration, they only provide
+          <link xlink:href="https://min.io/docs/minio/windows/operations/install-deploy-manage/migrate-fs-gateway.html">instructions
+          how to manually convert the node</link>. To facilitate this
+          migration we keep around the last version that still supports
+          the old filesystem backend as
+          <literal>minio_legacy_fs</literal>. Use it via
+          <literal>services.minio.package = minio_legacy_fs;</literal>
+          to export your data before switching to the new version. See
+          the corresponding
+          <link xlink:href="https://github.com/NixOS/nixpkgs/issues/199318">issue</link>
+          for more details.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>services.sourcehut.dispatch</literal> and the
+          corresponding package
+          (<literal>sourcehut.dispatchsrht</literal>) have been removed
+          due to
+          <link xlink:href="https://sourcehut.org/blog/2022-08-01-dispatch-deprecation-plans/">upstream
+          deprecation</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The
+          <link linkend="opt-services.snapserver.openFirewall">services.snapserver.openFirewall</link>
+          module option default value has been changed from
+          <literal>true</literal> to <literal>false</literal>. You will
+          need to explicitly set this option to <literal>true</literal>,
+          or configure your firewall.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The
+          <link linkend="opt-services.tmate-ssh-server.openFirewall">services.tmate-ssh-server.openFirewall</link>
+          module option default value has been changed from
+          <literal>true</literal> to <literal>false</literal>. You will
+          need to explicitly set this option to <literal>true</literal>,
+          or configure your firewall.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The
+          <link linkend="opt-services.unifi-video.openFirewall">services.unifi-video.openFirewall</link>
+          module option default value has been changed from
+          <literal>true</literal> to <literal>false</literal>. You will
+          need to explicitly set this option to <literal>true</literal>,
+          or configure your firewall.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The Nginx module now validates the syntax of config files at
+          build time. For more complex configurations (using
+          <literal>include</literal> with out-of-store files notably)
+          you may need to disable this check by setting
+          <link linkend="opt-services.nginx.validateConfig">services.nginx.validateConfig</link>
+          to <literal>false</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The EC2 image module previously detected and automatically
+          mounted ext3-formatted instance store devices and partitions
+          in stage-1 (initramfs), storing <literal>/tmp</literal> 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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          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.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          In <literal>mastodon</literal> it is now necessary to specify
+          location of file with <literal>PostgreSQL</literal> database
+          password. In
+          <literal>services.mastodon.database.passwordFile</literal>
+          parameter default value
+          <literal>/var/lib/mastodon/secrets/db-password</literal> has
+          been changed to <literal>null</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>nix.readOnlyStore</literal> option has been
+          renamed to <literal>boot.readOnlyNixStore</literal> to clarify
+          that it configures the NixOS boot process, not the Nix daemon.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+  <section xml:id="sec-release-23.05-notable-changes">
+    <title>Other Notable Changes</title>
+    <itemizedlist>
+      <listitem>
+        <para>
+          <literal>vim_configurable</literal> has been renamed to
+          <literal>vim-full</literal> to avoid confusion:
+          <literal>vim-full</literal>’s build-time features are
+          configurable, but both <literal>vim</literal> and
+          <literal>vim-full</literal> are
+          <emphasis>customizable</emphasis> (in the sense of user
+          configuration, like vimrc).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The module for the application firewall
+          <literal>opensnitch</literal> got the ability to configure
+          rules. Available as
+          <link linkend="opt-services.opensnitch.rules">services.opensnitch.rules</link>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The module <literal>usbmuxd</literal> now has the ability to
+          change the package used by the daemon. In case you’re
+          experiencing issues with <literal>usbmuxd</literal> you can
+          try an alternative program like <literal>usbmuxd2</literal>.
+          Available as
+          <link linkend="opt-services.usbmuxd.package">services.usbmuxd.package</link>
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>services.mastodon</literal> gained a tootctl wrapped
+          named <literal>mastodon-tootctl</literal> similar to
+          <literal>nextcloud-occ</literal> which can be executed from
+          any user and switches to the configured mastodon user with
+          sudo and sources the environment variables.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>dnsmasq</literal> service now takes configuration
+          via the <literal>services.dnsmasq.settings</literal> attribute
+          set. The option
+          <literal>services.dnsmasq.extraConfig</literal> will be
+          deprecated when NixOS 22.11 reaches end of life.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          To reduce closure size in
+          <literal>nixos/modules/profiles/minimal.nix</literal> profile
+          disabled installation documentations and manuals. Also
+          disabled <literal>logrotate</literal> and
+          <literal>udisks2</literal> services.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The minimal ISO image now uses the
+          <literal>nixos/modules/profiles/minimal.nix</literal> profile.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          <literal>mastodon</literal> now supports connection to a
+          remote <literal>PostgreSQL</literal> database.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The module <literal>services.headscale</literal> was
+          refactored to be compliant with
+          <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
+          0042</link>. To be precise, this means that the following
+          things have changed:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              Most settings has been migrated under
+              <link linkend="opt-services.headscale.settings">services.headscale.settings</link>
+              which is an attribute-set that will be converted into
+              headscale’s YAML config format. This means that the
+              configuration from
+              <link xlink:href="https://github.com/juanfont/headscale/blob/main/config-example.yaml">headscale’s
+              example configuration</link> can be directly written as
+              attribute-set in Nix within this option.
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
+          A new <literal>virtualisation.rosetta</literal> module was
+          added to allow running <literal>x86_64</literal> binaries
+          through
+          <link xlink:href="https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment">Rosetta</link>
+          inside virtualised NixOS guests on Apple silicon. This feature
+          works by default with the
+          <link xlink:href="https://docs.getutm.app/">UTM</link>
+          virtualisation
+          <link xlink:href="https://search.nixos.org/packages?channel=unstable&amp;show=utm&amp;from=0&amp;size=1&amp;sort=relevance&amp;type=packages&amp;query=utm">package</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The new option <literal>users.motdFile</literal> allows
+          configuring a Message Of The Day that can be updated
+          dynamically.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Enabling global redirect in
+          <literal>services.nginx.virtualHosts</literal> now allows one
+          to add exceptions with the <literal>locations</literal>
+          option.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          Resilio sync secret keys can now be provided using a secrets
+          file at runtime, preventing these secrets from ending up in
+          the Nix store.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>services.fwupd</literal> module now allows
+          arbitrary daemon settings to be configured in a structured
+          manner
+          (<link linkend="opt-services.fwupd.daemonSettings"><literal>services.fwupd.daemonSettings</literal></link>).
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The <literal>unifi-poller</literal> package and corresponding
+          NixOS module have been renamed to <literal>unpoller</literal>
+          to match upstream.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The new option
+          <literal>services.tailscale.useRoutingFeatures</literal>
+          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
+          <literal>server</literal>, otherwise if you wish to use an
+          exit node you can set this setting to
+          <literal>client</literal>. The strict RPF warning has been
+          removed as the RPF will be loosened automatically based on the
+          value of this setting.
+        </para>
+      </listitem>
+    </itemizedlist>
+  </section>
+</section>
diff --git a/nixos/doc/manual/installation/building-nixos.chapter.md b/nixos/doc/manual/installation/building-nixos.chapter.md
index 27d7e1d38553..7b0b5ea1c447 100644
--- a/nixos/doc/manual/installation/building-nixos.chapter.md
+++ b/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`.
@@ -18,9 +18,12 @@ enforced values with `mkForce`.
 
 ## Practical Instructions {#sec-building-image-instructions}
 
+To build an ISO image for the channel `nixos-unstable`:
+
 ```ShellSession
 $ 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
 ```
 
@@ -72,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/nixos/doc/manual/installation/installing-from-other-distro.section.md b/nixos/doc/manual/installation/installing-from-other-distro.section.md
index d9060eb89c37..36ef29d44639 100644
--- a/nixos/doc/manual/installation/installing-from-other-distro.section.md
+++ b/nixos/doc/manual/installation/installing-from-other-distro.section.md
@@ -148,7 +148,7 @@ 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
@@ -158,7 +158,7 @@ The first steps to all these are the same:
 
     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
+    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`)
@@ -177,7 +177,7 @@ The first steps to all these are the same:
     was probably single user):
 
     ```ShellSession
-    $ sudo chown -R 0.0 /nix
+    $ sudo chown -R 0:0 /nix
     ```
 
 1.  Set up the `/etc/NIXOS` and `/etc/NIXOS_LUSTRATE` files:
diff --git a/nixos/doc/manual/installation/installing-kexec.section.md b/nixos/doc/manual/installation/installing-kexec.section.md
new file mode 100644
index 000000000000..286cbbda6a69
--- /dev/null
+++ b/nixos/doc/manual/installation/installing-kexec.section.md
@@ -0,0 +1,64 @@
+# "Booting" into NixOS via kexec {#sec-booting-via-kexec}
+
+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.
+
+This is particularly useful for (cloud) providers where you can't boot a custom
+image, but get some Debian or Ubuntu installation.
+
+In these cases, it might be easier to use `kexec` to "jump into NixOS" from the
+running system, which only assumes `bash` and `kexec` to be installed on the
+machine.
+
+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.
+
+To build the necessary files from your current version of nixpkgs,
+you can run:
+
+```ShellSession
+nix-build -A kexec.x86_64-linux '<nixpkgs/nixos/release.nix>'
+```
+
+This will create a `result` directory containing the following:
+ - `bzImage` (the Linux kernel)
+ - `initrd` (the initrd file)
+ - `kexec-boot` (a shellscript invoking `kexec`)
+
+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
+`scp * root@$destination` to copy it over, rather than rsync.
+
+Once you finished copying, execute `kexec-boot` *on the destination*, and after
+some seconds, the machine should be booting into an (ephemeral) NixOS
+installation medium.
+
+In case you want to describe your own system closure to kexec into, instead of
+the default installer image, you can build your own `configuration.nix`:
+
+```nix
+{ modulesPath, ... }: {
+  imports = [
+    (modulesPath + "/installer/netboot/netboot-minimal.nix")
+  ];
+
+  services.openssh.enable = true;
+  users.users.root.openssh.authorizedKeys.keys = [
+    "my-ssh-pubkey"
+  ];
+}
+```
+
+
+```ShellSession
+nix-build '<nixpkgs/nixos>' \
+  --arg configuration ./configuration.nix
+  --attr config.system.build.kexecTree
+```
+
+Make sure your `configuration.nix` does still import `netboot-minimal.nix` (or
+`netboot-base.nix`).
diff --git a/nixos/doc/manual/installation/installing-usb.section.md b/nixos/doc/manual/installation/installing-usb.section.md
index d893e22e6381..da32935a7a10 100644
--- a/nixos/doc/manual/installation/installing-usb.section.md
+++ b/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/nixos/doc/manual/installation/installing-virtualbox-guest.section.md b/nixos/doc/manual/installation/installing-virtualbox-guest.section.md
index e9c2a621c1bb..c3bbfe12152e 100644
--- a/nixos/doc/manual/installation/installing-virtualbox-guest.section.md
+++ b/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
diff --git a/nixos/doc/manual/installation/installing.chapter.md b/nixos/doc/manual/installation/installing.chapter.md
index 8a46d68ae3ba..04bc7b1f2072 100644
--- a/nixos/doc/manual/installation/installing.chapter.md
+++ b/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 <kbd>F12</kbd>, but also <kbd>F1</kbd>,
+     <kbd>F9</kbd>, <kbd>F10</kbd>, <kbd>Enter</kbd>, <kbd>Del</kbd>,
+     <kbd>Esc</kbd> 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 <kbd>⌥</kbd>
+     (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 <kbd>Enter</kbd> 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,7 +227,8 @@ 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
 device.
@@ -133,14 +249,14 @@ update /etc/fstab.
     which will be used by the boot partition.
 
     ```ShellSession
-    # parted /dev/sda -- mkpart primary 512MiB -8GiB
+    # parted /dev/sda -- mkpart primary 512MB -8GB
     ```
 
 3.  Next, add a *swap* partition. The size required will vary according
-    to needs, here a 8GiB one is created.
+    to needs, here a 8GB one is created.
 
     ```ShellSession
-    # parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+    # parted /dev/sda -- mkpart primary linux-swap -8GB 100%
     ```
 
     ::: {.note}
@@ -153,14 +269,15 @@ update /etc/fstab.
     reserved 512MiB at the start of the disk.
 
     ```ShellSession
-    # parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+    # parted /dev/sda -- mkpart ESP fat32 1MB 512MB
     # parted /dev/sda -- set 3 esp on
     ```
 
 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
 the device.
@@ -180,14 +297,20 @@ update /etc/fstab.
     end part, where the swap will live.
 
     ```ShellSession
-    # parted /dev/sda -- mkpart primary 1MiB -8GiB
+    # parted /dev/sda -- mkpart primary 1MB -8GB
+    ```
+
+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
     ```
 
-3.  Finally, add a *swap* partition. The size required will vary
-    according to needs, here a 8GiB one is created.
+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 -8GiB 100%
+    # parted /dev/sda -- mkpart primary linux-swap -8GB 100%
     ```
 
     ::: {.note}
@@ -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.
@@ -303,7 +428,8 @@ Use the following commands:
 
     UEFI systems
 
-    :   You *must* set the option [](#opt-boot.loader.systemd-boot.enable)
+    :   You must select a boot-loader, either system-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.
 
@@ -312,6 +438,15 @@ 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
+        [](#opt-boot.loader.grub.efiSupport) to `true`.
+
+    :   With system-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
+        instead.
+
     If you need to configure networking for your machine the
     configuration options are described in [](#sec-networking). In
     particular, while wifi is supported on the installation image, it is
@@ -394,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
@@ -407,8 +543,8 @@ corresponding configuration Nix expression.
 :::
 ```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%
 ```
 :::
 
@@ -418,9 +554,9 @@ corresponding configuration Nix expression.
 :::
 ```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
 ```
 :::
@@ -476,6 +612,7 @@ With a partitioned disk.
 ```{=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" />
diff --git a/nixos/doc/manual/installation/obtaining.chapter.md b/nixos/doc/manual/installation/obtaining.chapter.md
index 832ec6146a9d..a72194ecf985 100644
--- a/nixos/doc/manual/installation/obtaining.chapter.md
+++ b/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/nixos/doc/manual/installation/upgrading.chapter.md b/nixos/doc/manual/installation/upgrading.chapter.md
index faeefc4451dc..249bcd97cec8 100644
--- a/nixos/doc/manual/installation/upgrading.chapter.md
+++ b/nixos/doc/manual/installation/upgrading.chapter.md
@@ -6,7 +6,7 @@ expressions and associated binaries. The NixOS channels are updated
 automatically from NixOS's Git repository after certain tests have
 passed and all packages have been built. These channels are:
 
--   *Stable channels*, such as [`nixos-21.11`](https://nixos.org/channels/nixos-21.11).
+-   *Stable channels*, such as [`nixos-22.11`](https://nixos.org/channels/nixos-22.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
@@ -19,7 +19,7 @@ passed and all packages have been built. These channels are:
     radical changes between channel updates. It's not recommended for
     production systems.
 
--   *Small channels*, such as [`nixos-21.11-small`](https://nixos.org/channels/nixos-21.11-small)
+-   *Small channels*, such as [`nixos-22.11-small`](https://nixos.org/channels/nixos-22.05-small)
     or [`nixos-unstable-small`](https://nixos.org/channels/nixos-unstable-small).
     These are identical to the stable and unstable channels described above,
     except that they contain fewer binary packages. This means they get updated
@@ -38,8 +38,8 @@ newest supported stable release.
 
 When you first install NixOS, you're automatically subscribed to the
 NixOS channel that corresponds to your installation source. For
-instance, if you installed from a 21.11 ISO, you will be subscribed to
-the `nixos-21.11` channel. To see which NixOS channel you're subscribed
+instance, if you installed from a 22.11 ISO, you will be subscribed to
+the `nixos-22.11` channel. To see which NixOS channel you're subscribed
 to, run the following as root:
 
 ```ShellSession
@@ -54,16 +54,16 @@ To switch to a different NixOS channel, do
 ```
 
 (Be sure to include the `nixos` parameter at the end.) For instance, to
-use the NixOS 21.11 stable channel:
+use the NixOS 22.11 stable channel:
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/nixos-21.11 nixos
+# nix-channel --add https://nixos.org/channels/nixos-22.11 nixos
 ```
 
 If you have a server, you may want to use the "small" channel instead:
 
 ```ShellSession
-# nix-channel --add https://nixos.org/channels/nixos-21.11-small nixos
+# nix-channel --add https://nixos.org/channels/nixos-22.11-small nixos
 ```
 
 And if you want to live on the bleeding edge:
@@ -114,5 +114,5 @@ the new generation contains a different kernel, initrd or kernel
 modules. You can also specify a channel explicitly, e.g.
 
 ```nix
-system.autoUpgrade.channel = https://nixos.org/channels/nixos-21.11;
+system.autoUpgrade.channel = https://nixos.org/channels/nixos-22.11;
 ```
diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml
index ea96f49fa977..cab871661a75 100644
--- a/nixos/doc/manual/man-nixos-rebuild.xml
+++ b/nixos/doc/manual/man-nixos-rebuild.xml
@@ -134,7 +134,7 @@
    </arg>
    <arg>
     <option>-I</option>
-    <replaceable>path</replaceable>
+    <replaceable>NIX_PATH</replaceable>
    </arg>
    <arg>
     <group choice='req'>
@@ -624,7 +624,7 @@
 
   <para>
    In addition, <command>nixos-rebuild</command> accepts various Nix-related
-   flags, including <option>--max-jobs</option> / <option>-j</option>,
+   flags, including <option>--max-jobs</option> / <option>-j</option>, <option>-I</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.
@@ -649,6 +649,20 @@
 
    <varlistentry>
     <term>
+     <envar>NIX_PATH</envar>
+    </term>
+    <listitem>
+     <para>
+      A colon-separated list of directories used to look up Nix expressions enclosed in angle brackets (e.g &lt;nixpkgs&gt;). Example
+      <screen>
+          nixpkgs=./my-nixpkgs
+      </screen>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
      <envar>NIX_SSHOPTS</envar>
     </term>
     <listitem>
diff --git a/nixos/doc/manual/md-to-db.sh b/nixos/doc/manual/md-to-db.sh
index 2091f9b31cd2..beb0ff9f7082 100755
--- a/nixos/doc/manual/md-to-db.sh
+++ b/nixos/doc/manual/md-to-db.sh
@@ -19,6 +19,7 @@ pandoc_flags=(
   "--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/html-elements.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
diff --git a/nixos/doc/manual/release-notes/release-notes.xml b/nixos/doc/manual/release-notes/release-notes.xml
index 216fea677757..bb5cc677afb8 100644
--- a/nixos/doc/manual/release-notes/release-notes.xml
+++ b/nixos/doc/manual/release-notes/release-notes.xml
@@ -8,6 +8,8 @@
   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-2305.section.xml" />
+ <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" />
diff --git a/nixos/doc/manual/release-notes/rl-1603.section.md b/nixos/doc/manual/release-notes/rl-1603.section.md
index dce879ec16d0..e4da7fd3094d 100644
--- a/nixos/doc/manual/release-notes/rl-1603.section.md
+++ b/nixos/doc/manual/release-notes/rl-1603.section.md
@@ -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.
 
diff --git a/nixos/doc/manual/release-notes/rl-1709.section.md b/nixos/doc/manual/release-notes/rl-1709.section.md
index e5af22721b0c..970a0c2b7dd1 100644
--- a/nixos/doc/manual/release-notes/rl-1709.section.md
+++ b/nixos/doc/manual/release-notes/rl-1709.section.md
@@ -238,7 +238,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - `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.
 
diff --git a/nixos/doc/manual/release-notes/rl-1903.section.md b/nixos/doc/manual/release-notes/rl-1903.section.md
index 7637a70c1bf8..e560b9f30448 100644
--- a/nixos/doc/manual/release-notes/rl-1903.section.md
+++ b/nixos/doc/manual/release-notes/rl-1903.section.md
@@ -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.
 
diff --git a/nixos/doc/manual/release-notes/rl-1909.section.md b/nixos/doc/manual/release-notes/rl-1909.section.md
index 572f1bf5a255..0f09f9b92734 100644
--- a/nixos/doc/manual/release-notes/rl-1909.section.md
+++ b/nixos/doc/manual/release-notes/rl-1909.section.md
@@ -154,13 +154,13 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - 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.
 
diff --git a/nixos/doc/manual/release-notes/rl-2105.section.md b/nixos/doc/manual/release-notes/rl-2105.section.md
index 359f2e5b2e58..77c4a9cd7a0a 100644
--- a/nixos/doc/manual/release-notes/rl-2105.section.md
+++ b/nixos/doc/manual/release-notes/rl-2105.section.md
@@ -369,7 +369,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - 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.
 
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 310d32cfdd72..fc4b44957c36 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -164,9 +164,11 @@ 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`, now requires detaching commands such as `succeed("foo &")` and `succeed("foo | xclip -i")` to close stdout.
+- 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.
   This can be done with a redirect such as `succeed("foo >&2 &")`. This breaking change was necessitated by a race condition causing tests to fail or hang.
   It applies to all methods that invoke commands on the nodes, including `execute`, `succeed`, `fail`, `wait_until_succeeds`, `wait_until_fails`.
 
@@ -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/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index 16c59ce3dddb..7cc0c308ee65 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -1,119 +1,177 @@
-# Release 22.05 (“Quokka”, 2022.05/??) {#sec-release-22.05}
-
-In addition to numerous new and upgraded packages, this release has the following highlights:
+# Release 22.05 (“Quokka”, 2022.05/30) {#sec-release-22.05}
 
 - Support is planned until the end of December 2022, handing over to 22.11.
 
 ## Highlights {#sec-release-22.05-highlights}
 
-- The `firefox` browser on `x86_64-linux` is now making use of
-  profile-guided optimization resulting in a much more responsive
-  browsing experience.
+In addition to numerous new and upgraded packages, this release has the following highlights:
 
-- `security.acme.defaults` has been added to simplify configuring
-  settings for many certificates at once. This also opens up the
-  the option to use DNS-01 validation when using `enableACME` on
-  web server virtual hosts (e.g. `services.nginx.virtualHosts.*.enableACME`).
+- 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),  
+  [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
+  [nix-2.8](https://nixos.org/manual/nix/stable/release-notes/rl-2.8.html)
 
-- GNOME has been upgraded to 42. Please take a look at their [Release Notes](https://release.gnome.org/42/) for details. Notably, it replaces gedit with GNOME Text Editor, GNOME Terminal with GNOME Console (formerly King’s Cross), and GNOME Screenshot with a tool built into the Shell.
+- The `firefox` browser on `x86_64-linux` now makes use of profile-guided
+  optimisation, resulting in a much more responsive browsing experience.
 
-- PHP 8.1 is now available
+- GNOME has been upgraded to 42. Please take a look at their [Release
+  Notes](https://release.gnome.org/42/) 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.
 
-- Mattermost has been updated to extended support release 6.3, as the previously packaged extended support release 5.37 is [reaching its end of life](https://docs.mattermost.com/upgrade/extended-support-release.html).
-  Migrations may take a while, 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).
+- PHP 8.1 is now available.
 
 - systemd services can now set [systemd.services.\<name\>.reloadTriggers](#opt-systemd.services) instead of `reloadIfChanged` for a more granular distinction between reloads and restarts.
 
 - Systemd has been upgraded to the version 250.
 
-- The new [`postgresqlTestHook`](https://nixos.org/manual/nixpkgs/stable/#sec-postgresqlTestHook) runs a PostgreSQL server for the duration of package checks.
+- 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
+  `hardware.pulseaudio.package = pkgs.pulseaudioFull;`. Existing third-party
+  modules that offered similar functions, such as `pulseaudio-modules-bt` or
+  `pulseaudio-hsphfpd`, are obsolete and have been removed.
 
-- [`kops`](https://kops.sigs.k8s.io) defaults to 1.22.4, 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. See the [release notes](https://kops.sigs.k8s.io/releases/1.22-notes/) for details.
+- PostgreSQL now defaults to major version 14.
 
 - Module authors can use `mkRenamedOptionModuleWith` to automate the deprecation cycle without annoying out-of-tree module authors and their users.
 
 - The default GHC version has been updated from 8.10.7 to 9.0.2. `pkgs.haskellPackages` and `pkgs.ghc` will now use this version by default.
 
+- The GNOME and Plasma installation CDs now use `pkgs.calamares` and `pkgs.calamares-nixos-extensions` to allow users to easily install and set up NixOS with a GUI.
+
+- `security.acme.defaults` 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 `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).
+
 - [aesmd](https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw), the Intel SGX Architectural Enclave Service Manager. Available as [services.aesmd](#opt-services.aesmd.enable).
 
-- [rootless Docker](https://docs.docker.com/engine/security/rootless/), a `systemd --user` Docker service which runs without root permissions. Available as [virtualisation.docker.rootless.enable](options.html#opt-virtualisation.docker.rootless.enable).
+- [agate](https://github.com/mbrubeck/agate), a very simple server for the Gemini hypertext protocol. Available as [services.agate](#opt-services.agate.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).
+- [apfs](https://github.com/linux-apfs/linux-apfs-rw), a kernel module for mounting the Apple File System (APFS).
 
-- [nethoscope](https://github.com/vvilhonen/nethoscope), listen to your network traffic. Available as [programs.nethoscope](#opt-programs.nethoscope.enable).
+- [argonone](https://gitlab.com/DarkElvenAngel/argononed), a replacement daemon for the Raspberry Pi Argon One power button and cooler. Available at [services.hardware.argonone](options.html#opt-services.hardware.argonone.enable).
+
+- [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).
+
+- [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).
+
+- [blocky](https://0xerr0r.github.io/blocky/), fast and lightweight DNS proxy as ad-blocker for local network with many features. Available as [services.blocky](#opt-services.blocky.enable).
+
+- [cloudflare-dyndns](https://github.com/kissgyorgy/cloudflare-dyndns), CloudFlare Dynamic DNS client. Available as [services.cloudflare-dyndns](#opt-services.cloudflare-dyndns.enable).
+
+- [Corosync](https://corosync.github.io/corosync/) and [Pacemaker](https://clusterlabs.org/pacemaker/), A open-source high availability resource manager. Available as [services.corosync](#opt-services.corosync.enable) and [services.pacemaker](#opt-services.pacemaker.enable).
+
+- [create_ap](https://github.com/lakinduakash/linux-wifi-hotspot), a module for creating wifi hotspots using the program linux-wifi-hotspot. Available as [services.create_ap](#opt-services.create_ap.enable).
+
+- [Envoy](https://www.envoyproxy.io/), a high-performance reverse proxy. Available as [services.envoy](#opt-services.envoy.enable).
+
+- [ergochat](https://ergo.chat), a modern IRC with IRCv3 features. Available as [services.ergochat](#opt-services.ergochat.enable).
+
+- [ethercalc](https://github.com/audreyt/ethercalc), an online collaborative spreadsheet. Available as [services.ethercalc](#opt-services.ethercalc.enable).
 
 - [filebeat](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-overview.html), a lightweight shipper for forwarding and centralizing log data. Available as [services.filebeat](#opt-services.filebeat.enable).
 
-- [apfs](https://github.com/linux-apfs/linux-apfs-rw), a kernel module for mounting the Apple File System (APFS).
+- [FRRouting](https://frrouting.org/), a popular suite of Internet routing protocol daemons (BGP, BFD, OSPF, IS-IS, VRRP and others). Available as [services.frr](#opt-services.frr.babel.enable).
 
-- [FRRouting](https://frrouting.org/), a popular suite of Internet routing protocol daemons (BGP, BFD, OSPF, IS-IS, VRRP and others). Available as [services.frr](#opt-services.frr.babel.enable)
+- [Grafana Mimir](https://grafana.com/oss/mimir/), an open source, horizontally scalable, highly available, multi-tenant, long-term storage for Prometheus. Available as [services.mimir](#opt-services.mimir.enable).
 
-- [heisenbridge](https://github.com/hifi/heisenbridge), a bouncer-style Matrix IRC bridge. Available as [services.heisenbridge](options.html#opt-services.heisenbridge.enable).
+- [Haste](https://hastebin.com/about.md), a pastebin written in node.js. Available as [services.haste](#opt-services.haste-server.enable).
 
-- [snowflake-proxy](https://snowflake.torproject.org/), a system to defeat internet censorship. Available as [services.snowflake-proxy](options.html#opt-services.snowflake-proxy.enable).
+- [headscale](https://github.com/juanfont/headscale), an Open Source implementation of the [Tailscale](https://tailscale.io) Control Server. Available as [services.headscale](#opt-services.headscale.enable).
 
-- [ergochat](https://ergo.chat), a modern IRC with IRCv3 features. Available as [services.ergochat](options.html#opt-services.ergochat.enable).
+- [heisenbridge](https://github.com/hifi/heisenbridge), a bouncer-style Matrix IRC bridge. Available as [services.heisenbridge](#opt-services.heisenbridge.enable).
 
-- [PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin), a web interface for the PowerDNS server. Available at [services.powerdns-admin](options.html#opt-services.powerdns-admin.enable).
+- [https-dns-proxy](https://github.com/aarond10/https_dns_proxy), DNS to DNS over HTTPS (DoH) proxy. Available as [services.https-dns-proxy](#opt-services.https-dns-proxy.enable).
 
-- [pgadmin4](https://github.com/postgres/pgadmin4), an admin interface for the PostgreSQL database. Available at [services.pgadmin](options.html#opt-services.pgadmin.enable).
+- [input-remapper](https://github.com/sezanzeb/input-remapper), an easy to use tool to change the mapping of your input device buttons. Available at [services.input-remapper](#opt-services.input-remapper.enable).
 
-- [input-remapper](https://github.com/sezanzeb/input-remapper), an easy to use tool to change the mapping of your input device buttons. Available at [services.input-remapper](options.html#opt-services.input-remapper.enable).
+- [InvoicePlane](https://invoiceplane.com), web application for managing and creating invoices. Available at [services.invoiceplane](#opt-services.invoiceplane.sites._name_.enable).
 
-- [InvoicePlane](https://invoiceplane.com), web application for managing and creating invoices. Available at [services.invoiceplane](options.html#opt-services.invoiceplane.enable).
+- [k3b](https://userbase.kde.org/K3b), the KDE disk burning application. Available as [programs.k3b](#opt-programs.k3b.enable).
 
-- [maddy](https://maddy.email), a composable all-in-one mail server. Available as [services.maddy](options.html#opt-services.maddy.enable).
+- [K40-Whisperer](https://www.scorchworks.com/K40whisperer/k40whisperer.html), a program to control cheap Chinese laser cutters. Available as [programs.k40-whisperer.enable](#opt-programs.k40-whisperer.enable). Users must add themselves to the `k40` group to be able to access the device.
 
-- [K40-Whisperer](https://www.scorchworks.com/K40whisperer/k40whisperer.html), a program to control cheap Chinese laser cutters. Available as [programs.k40-whisperer.enable](options.html#opt-programs.k4-whisperer.enable). Users must add themselves to the `k40` group to be able to access the device.
+- [kanidm](https://kanidm.github.io/kanidm/stable/), an identity management server written in Rust. Available as [services.kanidm](#opt-services.kanidm.enableServer)
 
-- [mozillavpn](https://github.com/mozilla-mobile/mozilla-vpn-client), the client for the [Mozilla VPN](https://vpn.mozilla.org/) service. Available as [services.mozillavpn](options.html#opt-services.mozillavpn).
+- [Maddy](https://maddy.email/), a free an open source mail server. Available as [services.maddy](#opt-services.maddy.enable).
 
-- [mtr-exporter](https://github.com/mgumz/mtr-exporter), a Prometheus exporter for mtr metrics. Available as [services.mtr-exporter](options.html#opt-services.mtr-exporter.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).
 
-- [prometheus-pve-exporter](https://github.com/prometheus-pve/prometheus-pve-exporter), a tool that exposes information from the Proxmox VE API for use by Prometheus. Available as [services.prometheus.exporters.pve](options.html#opt-services.prometheus.exporters.pve).
+- [Moosefs](https://moosefs.com), fault tolerant petabyte distributed file system. Available as [moosefs](#opt-services.moosefs.master.enable).
 
-- [netbox](https://github.com/netbox-community/netbox), infrastructure resource modeling (IRM) tool. Available as [services.netbox](options.html#opt-services.netbox.enable).
+- [mozillavpn](https://github.com/mozilla-mobile/mozilla-vpn-client), the client for the [Mozilla VPN](https://vpn.mozilla.org/) service. Available as [services.mozillavpn](#opt-services.mozillavpn.enable).
 
-- [tetrd](https://tetrd.app), share your internet connection from your device to your PC and vice versa through a USB cable. Available at [services.tetrd](#opt-services.tetrd.enable).
+- [mtr-exporter](https://github.com/mgumz/mtr-exporter), a Prometheus exporter for mtr metrics. Available as [services.mtr-exporter](#opt-services.mtr-exporter.enable).
 
-- [agate](https://github.com/mbrubeck/agate), a very simple server for the Gemini hypertext protocol. Available as [services.agate](options.html#opt-services.agate.enable).
+- [nbd](https://nbd.sourceforge.io/), a Network Block Device server. Available as [services.nbd](#opt-services.nbd.server.enable).
 
-- [ArchiSteamFarm](https://github.com/JustArchiNET/ArchiSteamFarm), a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. Available as [services.archisteamfarm](options.html#opt-services.archisteamfarm.enable).
+- [netbox](https://github.com/netbox-community/netbox), infrastructure resource modeling (IRM) tool. Available as [services.netbox](#opt-services.netbox.enable).
 
-- [teleport](https://goteleport.com), allows engineers and security professionals to unify access for SSH servers, Kubernetes clusters, web applications, and databases across all environments. Available at [services.teleport](#opt-services.teleport.enable).
+- [nethoscope](https://github.com/vvilhonen/nethoscope), listen to your network traffic. Available as [programs.nethoscope](#opt-programs.nethoscope.enable).
 
-- [BaGet](https://loic-sharma.github.io/BaGet/), a lightweight NuGet and symbol server. Available at [services.baget](#opt-services.baget.enable).
+- [nifi](https://nifi.apache.org), an easy to use, powerful, and reliable system to process and distribute data. Available as [services.nifi](#opt-services.nifi.enable).
+
+- [nix-ld](https://github.com/Mic92/nix-ld), Run unpatched dynamic binaries on NixOS. Available as [programs.nix-ld](#opt-programs.nix-ld.enable).
 
-- [moosefs](https://moosefs.com), fault tolerant petabyte distributed file system.
-  Available as [moosefs](#opt-services.moosefs.client.enable).
+- [NNCP](http://www.nncpgo.org), NNCP (Node to Node copy) utilities and configuration, Available as [programs.nncp](#opt-programs.nncp.enable).
+
+- [pgadmin4](https://github.com/postgres/pgadmin4), an admin interface for the PostgreSQL database. Available at [services.pgadmin](#opt-services.pgadmin.enable).
+
+- [PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin), a web interface for the PowerDNS server. Available at [services.powerdns-admin](#opt-services.powerdns-admin.enable).
+
+- [prometheus-pve-exporter](https://github.com/prometheus-pve/prometheus-pve-exporter), a tool that exposes information from the Proxmox VE API for use by Prometheus. Available as [services.prometheus.exporters.pve](#opt-services.prometheus.exporters.pve.enable).
 
 - [prosody-filer](https://github.com/ThomasLeister/prosody-filer), a server for handling XMPP HTTP Upload requests. Available at [services.prosody-filer](#opt-services.prosody-filer.enable).
 
-- [systembus-notify](https://github.com/rfjakob/systembus-notify), allow system level notifications to reach the users. Available as [services.systembus-notify](opt-services.systembus-notify.enable). 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.
+- [Public Inbox](https://public-inbox.org), an "archives first" approach to mailing lists. Available as [services.public-inbox](#opt-services.public-inbox.enable).
+
+- [r53-ddns](https://github.com/fleaz/r53-ddns), a small tool to run your own DDNS service via AWS Route53. Available as [services.r53-ddns](#opt-services.r53-ddns.enable).
+
+- [rmfakecloud](https://ddvk.github.io/rmfakecloud/), a clone of the cloud sync the remarkable tablet. Available as [services.rmfakecloud](#opt-services.rmfakecloud.enable).
+
+- [rootless Docker](https://docs.docker.com/engine/security/rootless/), a `systemd --user` Docker service which runs without root permissions. Available as [virtualisation.docker.rootless.enable](#opt-virtualisation.docker.rootless.enable).
+
+- [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).
 
-- [ethercalc](https://github.com/audreyt/ethercalc), an online collaborative
-  spreadsheet. Available as [services.ethercalc](options.html#opt-services.ethercalc.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).
 
-- [nbd](https://nbd.sourceforge.io/), a Network Block Device server. Available as [services.nbd](options.html#opt-services.nbd.server.enable).
+- [snowflake-proxy](https://snowflake.torproject.org/), a system to defeat internet censorship. Available as [services.snowflake-proxy](#opt-services.snowflake-proxy.enable).
 
-- [nix-ld](https://github.com/Mic92/nix-ld), Run unpatched dynamic binaries on NixOS. Available as [programs.nix-ld](options.html#opt-programs.nix-ld.enable).
+- [sslmate-agent](https://sslmate.com/), a daemon for managing SSL/TLS certificates on a server. Available as [services.sslmate-agent](services.sslmate-agent.enable).
 
-- [timetagger](https://timetagger.app), an open source time-tracker with an intuitive user experience and powerful reporting. [services.timetagger](options.html#opt-services.timetagger.enable).
+- [starship](https://starship.rs), a minimal, blazing-fast, and infinitely customizable prompt for any shell. Available at [programs.startship](#opt-programs.starship.enable).
 
-- [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](options.html#opt-services.rstudio-server.enable).
+- [systembus-notify](https://github.com/rfjakob/systembus-notify), allow system level notifications to reach the users. Available as [services.systembus-notify](opt-services.systembus-notify.enable). 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.
+
+- [teleport](https://goteleport.com), allows engineers and security professionals to unify access for SSH servers, Kubernetes clusters, web applications, and databases across all environments. Available at [services.teleport](#opt-services.teleport.enable).
+
+- [tetrd](https://tetrd.app), share your internet connection from your device to your PC and vice versa through a USB cable. Available at [services.tetrd](#opt-services.tetrd.enable).
 
-- [headscale](https://github.com/juanfont/headscale), an Open Source implementation of the [Tailscale](https://tailscale.io) Control Server. Available as [services.headscale](options.html#opt-services.headscale.enable)
+- [uptermd](https://upterm.dev), an open-source solution for sharing terminal sessions instantly over the public internet via secure tunnels. Available at [services.uptermd](#opt-services.uptermd.enable).
 
-- [create_ap](https://github.com/lakinduakash/linux-wifi-hotspot), a module for creating wifi hotspots using the program linux-wifi-hotspot. Available as [services.create_ap](options.html#opt-services.create_ap.enable).
+- [usbrelayd](https://github.com/darrylb123/usbrelay), an USB Relay MQTT daemon. Available as [services.usbrelayd](#opt-services.usbrelayd.enable).
 
-- [blocky](https://0xerr0r.github.io/blocky/), fast and lightweight DNS proxy as ad-blocker for local network with many features.
+- [webdav-server-rs](https://github.com/miquels/webdav-server-rs), Webdav server in rust. Available as [services.webdav-server-rs](#opt-services.webdav-server-rs.enable).
 
-- [pacemaker](https://clusterlabs.org/pacemaker/) cluster resource manager
+- [wg-netmanager](https://github.com/gin66/wg_netmanager), the Wireguard network manager. Available as [services.wg-netmanager](#opt-services.wg-netmanager.enable).
 
-- [nifi](https://nifi.apache.org), an easy to use, powerful, and reliable system to process and distribute data. Available as [services.nifi](options.html#opt-services.nifi.enable).
+- [Zammad](https://zammad.org/), a web-based, open source user support/ticketing solution. Available as [services.zammad](#opt-services.zammad.enable).
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
@@ -137,6 +195,10 @@ In addition to numerous new and upgraded packages, this release has the followin
   `useLLVM`. So instead of `(ghc.withPackages (p: [])).override { withLLVM = true; }`,
   one needs to use `(ghc.withPackages.override { useLLVM = true; }) (p: [])`.
 
+- The update of the haskell package set brings with it a new version of the `xmonad`
+  module, which will break your configuration if you use `launch` as entrypoint. The
+  example code the corresponding nixos module was adjusted, you may want to have a look at it.
+
 - The `home-assistant` module now requires users that don't want their
   configuration to be managed declaratively to set
   `services.home-assistant.config = null;`. This is required
@@ -151,6 +213,21 @@ In addition to numerous new and upgraded packages, this release has the followin
   org-contrib, refer to the ones in `pkgs.emacsPackages.elpaPackages` and
   `pkgs.emacsPackages.nongnuPackages` where the new versions will release.
 
+- The configuration and state directories used by `nixos-containers` have been
+  moved from `/etc/containers` and `/var/lib/containers` to
+  `/etc/nixos-containers` and `/var/lib/nixos-containers`.
+
+  If you are changing `system.stateVersion` to `"22.05"` manually on an existing
+  system you are responsible for migrating these directories yourself.
+
+  This is to improve compatibility with `libcontainer` based software such as Podman and Skopeo
+  which assumes they have ownership over `/etc/containers`.
+
+- `lib.systems.supported` 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 `lib.systems.flakeExposed`.
+
+- For new installations `virtualisation.oci-containers.backend` is now set to `podman` by default.
+  If you still want to use Docker on systems where `system.stateVersion` is set to to `"22.05"` set `virtualisation.oci-containers.backend = "docker";`.Old systems with older `stateVersion`s stay with "docker".
+
 - `security.klogd` was removed.  Logging of kernel messages is handled
   by systemd since Linux 3.5.
 
@@ -199,6 +276,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - In the ncdns module, the default value of `services.ncdns.address` has been changed to the IPv6 loopback address (`::1`).
 
+- `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`.
+
 - `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`.
@@ -317,18 +398,20 @@ In addition to numerous new and upgraded packages, this release has the followin
       };
 
       extraConfigFiles = [
-        /run/keys/matrix-synapse/secrets.yaml
+        "/run/keys/matrix-synapse/secrets.yaml"
       ];
     };
   }
   ```
 
-  The secrets in your original config should be migrated into a YAML file that is included via `extraConfigFiles`.
+  The secrets in your original config should be migrated into a YAML file that is included via `extraConfigFiles`. The filename must be quoted to prevent nix from copying it to the (world readable) store.
 
   Additionally a few option defaults have been synced up with upstream default values, for example the `max_upload_size` grew from `10M` to `50M`. For the same reason, the default
   `media_store_path` was changed from `${dataDir}/media` to `${dataDir}/media_store` if `system.stateVersion` is at least `22.05`. Files will need to be manually moved to the new
   location if the `stateVersion` is updated.
 
+  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.
+
 - The Keycloak package (`pkgs.keycloak`) has been switched from the
   Wildfly version, which will soon be deprecated, to the Quarkus based
   version. The Keycloak service (`services.keycloak`) has been updated
@@ -451,6 +534,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `autorestic` package has been upgraded from 1.3.0 to 1.5.0 which introduces breaking changes in config file, check [their migration guide](https://autorestic.vercel.app/migration/1.4_1.5) for more details.
 
+- `teleport` has been upgraded to major version 9. Please see upstream [upgrade instructions](https://goteleport.com/docs/setup/operations/upgrading/) and [release notes](https://goteleport.com/docs/changelog/#900).
+
 - For `pkgs.python3.pkgs.ipython`, its direct dependency `pkgs.python3.pkgs.matplotlib-inline`
   (which is really an adapter to integrate matplotlib in ipython if it is installed) does
   not depend on `pkgs.python3.pkgs.matplotlib` anymore.
@@ -477,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.
 
@@ -496,11 +581,19 @@ 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`
 
-- `systemd-shutdown` is now properly linked on shutdown to unmount all filesystems and device mapper devices cleanly. This can be disabled using `boot.systemd.shutdown.enable`.
+- `systemd-shutdown` is now properly linked on shutdown to unmount all filesystems and device mapper devices cleanly. This can be disabled using `systemd.shutdownRamfs.enable`.
 
 - The Tor SOCKS proxy is now actually disabled if `services.tor.client.enable` is set to `false` (the default). If you are using this functionality but didn't change the setting or set it to `false`, you now need to set it to `true`.
 
@@ -528,8 +621,14 @@ In addition to numerous new and upgraded packages, this release has the followin
   you should change the package you refer to. If you don't need them update your
   commands from `otelcontribcol` to `otelcorecol` and enjoy a 7x smaller binary.
 
+- `services.zookeeper` has a new option `jre` for specifying the JRE to start
+  zookeeper with. It defaults to the JRE that `pkgs.zookeeper` was wrapped with,
+  instead of `pkgs.jre`. This changes the JRE to `pkgs.jdk11_headless` by default.
+
 - `pkgs.pgadmin` now refers to `pkgs.pgadmin4`. `pgadmin3` has been removed.
 
+- `pkgs.minetestclient_4` and `pkgs.minetestserver_4` have been removed, as the last 4.x release was in 2018. `pkgs.minetestclient` (equivalent to `pkgs.minetest` ) and `pkgs.minetestserver` can be used instead.
+
 - `pkgs.noto-fonts-cjk` is now deprecated in favor of `pkgs.noto-fonts-cjk-sans`
   and `pkgs.noto-fonts-cjk-serif` because they each have different release
   schedules. To maintain compatibility with prior releases of Nixpkgs,
@@ -579,6 +678,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `vpnc` package has been changed to use GnuTLS instead of OpenSSL by default for licensing reasons.
 
+- The default version of `nextcloud` is **nextcloud24**. Please note that it's **not** possible to upgrade
+  `nextcloud` across multiple major versions! This means it's e.g. not possible to upgrade from `nextcloud22`
+  to `nextcloud24` in a single deploy and most `21.11` users will have to upgrade to `nextcloud23` first.
+
 - `pkgs.vimPlugins.onedark-nvim` now refers to [navarasu/onedark.nvim](https://github.com/navarasu/onedark.nvim)
   (formerly refers to [olimorris/onedarkpro.nvim](https://github.com/olimorris/onedarkpro.nvim)).
 
@@ -640,6 +743,13 @@ 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). 
+  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).
 
 - The `writers.writePyPy2`/`writers.writePyPy3` and corresponding `writers.writePyPy2Bin`/`writers.writePyPy3Bin` convenience functions to create executable Python 2/3 scripts using the PyPy interpreter were added.
 
@@ -652,6 +762,32 @@ 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. 
+  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/), 
+  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. 
+  >  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
+  cp /var/lib/redis/dump.rdb "/var/lib/redis-mastodon/dump.rdb"
+  ```
+- 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.
+
+  Redis database is used for storage only cache and job queue. More information can be found here - [Peertube architecture](https://docs.joinpeertube.org/contribute-architecture).
+
+  If you do want to save the redis database, you can use the following commands before upgrade OS:
+  ```bash
+  redis-cli save
+  sudo mkdir /var/lib/redis-peertube
+  sudo cp /var/lib/redis/dump.rdb /var/lib/redis-peertube/dump.rdb
+  ```
+- Added the `keter` NixOS module. Keter reverse proxies requests to your loaded application based on virtual hostnames.
+
 - 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
   `NIXOS_OZONE_WL=1` (for example via
@@ -718,7 +854,6 @@ In addition to numerous new and upgraded packages, this release has the followin
   If you are using only a window manager without a desktop manager, you need to enable
   `services.xserver.desktopManager.runXdgAutostartIfNone` or using the `dex` package to make `fcitx5` work.
 
-- A new module was added for the Envoy reverse proxy, providing the options `services.envoy.enable` and `services.envoy.settings`.
 
 - The option `services.duplicati.dataDir` has been added to allow changing the location of duplicati's files.
 
@@ -764,9 +899,6 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The default value for `programs.spacefm.settings.graphical_su` got unset. It previously pointed to `gksu` which has been removed.
 
-- A new module was added for the [Starship](https://starship.rs/) shell prompt,
-  providing the options `programs.starship.enable` and `programs.starship.settings`.
-
 - The [Dino](https://dino.im) XMPP client was updated to 0.3, adding support for audio and video calls.
 
 - `services.mattermost.plugins` has been added to allow the declarative installation of Mattermost plugins.
@@ -774,14 +906,16 @@ 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).
 
 - The `vscode-extensions.ionide.ionide-fsharp` package has been updated to 6.0.0 and now requires .NET 6.0.
 
+- The `phpPackages.box` package has been updated from 2.7.5 to 3.16.0. See the [upgrade guide](https://github.com/box-project/box/blob/master/UPGRADE.md#from-27-to-30) for more details.
+
 - The `zrepl` package has been updated from 0.4.0 to 0.5:
 
   - The RPC protocol version was bumped; all zrepl daemons in a setup must be updated and restarted before replication can resume.
@@ -811,6 +945,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The polkit service, available at `security.polkit.enable`, is now disabled by default. It will automatically be enabled through services and desktop environments as needed.
 
+- `mercury` was updated to 22.01.1, which has some breaking changes ([Mercury 22.01 news](https://dl.mercurylang.org/release/release-notes-22.01.html)).
+
 - 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 `mkfs.xfs -m bigtime=0 -m inobtcount=0`.
 
@@ -822,18 +958,46 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `nss` package was split into `nss_esr` and `nss_latest`, with `nss` being an alias for `nss_esr`. This was done to ease maintenance of `nss` and dependent high-profile packages like `firefox`.
 
+- The default `scribus` version is now 1.5, while version 1.4 is still available as `scribus_1_4` ([#172700](https://github.com/NixOS/nixpkgs/pull/172700)).
+
 - The Nextcloud module now supports to create a Mysql database automatically
   with `services.nextcloud.database.createLocally` enabled.
 
+- The Nextcloud module now allows setting the value of the `max-age` directive of the `Strict-Transport-Security` HTTP header, which is now controlled by the `services.nextcloud.https` option, rather than `services.nginx.recommendedHttpHeaders`.
+
 - The `spark3` package has been updated from 3.1.2 to 3.2.1 ([#160075](https://github.com/NixOS/nixpkgs/pull/160075)):
 
   - Testing has been enabled for `aarch64-linux` in addition to `x86_64-linux`.
   - The `spark3` package is now usable on `aarch64-darwin` as a result of [#158613](https://github.com/NixOS/nixpkgs/pull/158613) and [#158992](https://github.com/NixOS/nixpkgs/pull/158992).
 
-- The `programs.nncp` options were added for generating host-global NNCP configuration.
-
 - The option `services.snapserver.openFirewall` will no longer default to
   `true` starting with NixOS 22.11. Enable it explicitly if you need to control
   Snapserver remotely or connect streamig clients from other hosts.
 
+- The option [networking.useDHCP](options.html#opt-networking.useDHCP) isn't deprecated anymore.
+  When using [`systemd-networkd`](options.html#opt-networking.useNetworkd), a generic
+  `.network`-unit is added which enables DHCP for each interface matching `en*`, `eth*`
+  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.
+
+- `stdenv.mkDerivation` now supports a self-referencing `finalAttrs:` parameter
+  containing the final `mkDerivation` arguments including overrides.
+  `drv.overrideAttrs` now supports two parameters `finalAttrs: previousAttrs:`.
+  This allows packaging configuration to be overridden in a consistent manner by
+  providing an alternative to `rec {}` syntax.
+
+  Additionally, `passthru` can now reference `finalAttrs.finalPackage` containing
+  the final package, including attributes such as the output paths and
+  `overrideAttrs`.
+
+  New language integrations can be simplified by overriding a "prototype"
+  package containing the language-specific logic. This removes the need for a
+  extra layer of overriding for the "generic builder" arguments, thus removing a
+  usability problem and source of error.
+
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
new file mode 100644
index 000000000000..e92c776b33e3
--- /dev/null
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -0,0 +1,536 @@
+# Release 22.11 (“Raccoon”, 2022.11/30) {#sec-release-22.11}
+
+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 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.
+
+- `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 `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.
+   - `buildPlatform` is the platform that is responsible for building the NixOS
+     configuration. It defaults to the `hostPlatform`, for a non-cross
+     build configuration. To cross compile, set `buildPlatform` to a different
+     value.
+
+  The new options convey the same information, but with fewer options, and
+  following the Nixpkgs terminology.
+
+  The existing options `nixpkgs.{system,localSystem,crossSystem}` 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.
+
+## Notable version updates {#sec-release-22.11-version-updates}
+
+- 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).
+
+- OpenSSL now defaults to OpenSSL 3, updated from 1.1.1.
+
+- GNOME has been upgraded to version 43. Please see the [release notes](https://release.gnome.org/43/) for details.
+
+- 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.
+
+- 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.
+
+- PHP now defaults to PHP 8.1, updated from 8.0.
+
+- Perl has been updated to 5.36, and its core module `HTTP::Tiny` was patched to verify SSL/TLS certificates by default.
+
+- 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
+  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 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" ]``.
+
+- 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,
+  as it requires `qt4`, which reached its end-of-life 2015 and will no longer be supported by nixpkgs.
+  [According to Barco](https://www.barco.com/de/support/knowledge-base/4380-can-i-use-linux-os-with-clickshare-base-units) many of their base unit models can be used with Google Chrome and the Google Cast extension.
+
+- `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.
+
+- 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"`.
+
+- 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 maintainance 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.
+
+- 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 neccesary.
+- `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 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 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.
+
+- 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.
+
+- 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.
+
+- 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.
+
+- 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).
+
+- [Uptime Kuma](https://uptime.kuma.pet/), a fancy self-hosted monitoring tool. Available as [services.uptime-kuma](#opt-services.uptime-kuma.enable).
+
+- [WriteFreely](https://writefreely.org), a simple blogging platform with ActivityPub support. Available as [services.writefreely](options.html#opt-services.writefreely.enable).
+
+- [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/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
new file mode 100644
index 000000000000..911575d8ab53
--- /dev/null
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -0,0 +1,102 @@
+# Release 23.05 (“Stoat”, 2023.05/??) {#sec-release-23.05}
+
+Support is planned until the end of December 2023, handing over to 23.11.
+
+## Highlights {#sec-release-23.05-highlights}
+
+In addition to numerous new and upgraded packages, this release has the following highlights:
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- Cinnamon has been updated to 5.6, see [the pull request](https://github.com/NixOS/nixpkgs/pull/201328#issue-1449910204) for what is changed.
+
+## New Services {#sec-release-23.05-new-services}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- [blesh](https://github.com/akinomyoga/ble.sh), a line editor written in pure bash. Available as [programs.bash.blesh](#opt-programs.bash.blesh.enable).
+
+- [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
+
+- [atuin](https://github.com/ellie/atuin), a sync server for shell history. Available as [services.atuin](#opt-services.atuin.enable).
+
+- [mmsd](https://gitlab.com/kop316/mmsd), a lower level daemon that transmits and recieves MMSes. Available as [services.mmsd](#opt-services.mmsd.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).
+
+## Backward Incompatibilities {#sec-release-23.05-incompatibilities}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list 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.
+
+- `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 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`
+
+- `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 a 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 [services.snapserver.openFirewall](#opt-services.snapserver.openFirewall) module option default value has been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall.
+
+- The [services.tmate-ssh-server.openFirewall](#opt-services.tmate-ssh-server.openFirewall) module option default value has been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall.
+
+- The [services.unifi-video.openFirewall](#opt-services.unifi-video.openFirewall) module option default value has been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall.
+
+- The Nginx module now validates the syntax of config files at build time. For more complex configurations (using `include` with out-of-store files notably) you may need to disable this check by setting [services.nginx.validateConfig](#opt-services.nginx.validateConfig) to `false`.
+
+- 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.
+
+- 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.
+
+- 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.
+
+## 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. -->
+
+- `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).
+
+- 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)
+
+- `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.
+
+- 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.
+
+- To reduce closure size in `nixos/modules/profiles/minimal.nix` profile disabled installation documentations and manuals. Also disabled `logrotate` and `udisks2` services.
+
+- The minimal ISO image now uses the `nixos/modules/profiles/minimal.nix` profile.
+
+- `mastodon` now supports connection to a remote `PostgreSQL` database.
+
+- 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 has been migrated under [services.headscale.settings](#opt-services.headscale.settings) which is an 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.
+
+- 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=unstable&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.
+
+- Enabling global redirect in `services.nginx.virtualHosts` now allows one to add exceptions with the `locations` option.
+
+- 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 `services.fwupd` module now allows arbitrary daemon settings to be configured in a structured manner ([`services.fwupd.daemonSettings`](#opt-services.fwupd.daemonSettings)).
+
+- The `unifi-poller` package and corresponding NixOS module have been renamed to `unpoller` to match upstream.
+
+- 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.
diff --git a/nixos/lib/build-vms.nix b/nixos/lib/build-vms.nix
deleted file mode 100644
index 05d9ce89dbdc..000000000000
--- a/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. 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/nixos/lib/default.nix b/nixos/lib/default.nix
index 2b3056e01457..65d91342d4d1 100644
--- a/nixos/lib/default.nix
+++ b/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/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index 2daaa8a11863..1e086271e523 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -9,12 +9,16 @@
 # expressions are ever made modular at the top level) can just use
 # types.submodule instead of using eval-config.nix
 evalConfigArgs@
-{ # !!! system can be set modularly, would be nice to remove
+{ # !!! system can be set modularly, would be nice to remove,
+  #     however, removing or changing this default is too much
+  #     of a breaking change. To set it modularly, pass `null`.
   system ? builtins.currentSystem
 , # !!! is this argument needed any more? The pkgs argument can
   # 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 ? {}
@@ -48,12 +52,7 @@ let
       # 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;
-
-      # Stash the value of the `system` argument. When using `nesting.children`
-      # we want to have the same default value behavior (immediately above)
-      # without any interference from the user's configuration.
-      nixpkgs.initialSystem = system;
+      nixpkgs.system = lib.mkIf (system != null) (lib.mkDefault system);
 
       _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_);
     };
diff --git a/nixos/lib/make-ext4-fs.nix b/nixos/lib/make-ext4-fs.nix
index 416beeb32f2f..b8e1b8d24c48 100644
--- a/nixos/lib/make-ext4-fs.nix
+++ b/nixos/lib/make-ext4-fs.nix
@@ -78,6 +78,15 @@ pkgs.stdenv.mkDerivation {
       # get rid of the unnecessary slack here--but see
       # https://github.com/NixOS/nixpkgs/issues/125121 for caveats.
 
+      # shrink to fit
+      resize2fs -M $img
+
+      # Add 16 MebiByte to the current_size
+      new_size=$(dumpe2fs -h $img | awk -F: \
+        '/Block count/{count=$2} /Block size/{size=$2} END{print (count*size+16*2**20)/size}')
+
+      resize2fs $img $new_size
+
       if [ ${builtins.toString compressImage} ]; then
         echo "Compressing image"
         zstd -v --no-progress ./$img -o $out
diff --git a/nixos/lib/make-multi-disk-zfs-image.nix b/nixos/lib/make-multi-disk-zfs-image.nix
index a84732aa1171..f9046a485a7d 100644
--- a/nixos/lib/make-multi-disk-zfs-image.nix
+++ b/nixos/lib/make-multi-disk-zfs-image.nix
@@ -128,7 +128,7 @@ let
       gptfdisk
       nix
       parted
-      utillinux
+      util-linux
       zfs
     ]
   );
@@ -143,13 +143,6 @@ let
       properties
   );
 
-  featuresToProperties = features:
-    lib.listToAttrs
-      (builtins.map (feature: {
-        name = "feature@${feature}";
-        value = "enabled";
-      }) features);
-
   createDatasets =
     let
       datasetlist = lib.mapAttrsToList lib.nameValuePair datasets;
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix
index 57652dd5db1e..a3436caad8f9 100644
--- a/nixos/lib/make-options-doc/default.nix
+++ b/nixos/lib/make-options-doc/default.nix
@@ -19,7 +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
@@ -28,28 +36,20 @@
 # 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.
+, 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;
-
-  optionsList = lib.flip map optionsListVisible
-   (opt: transformOptions 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; }
+  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 ? relatedPackages && opt.relatedPackages != []) { relatedPackages = genRelatedPackages opt.relatedPackages opt.name; }
    );
 
@@ -88,47 +88,60 @@ let
         '';
     in "<itemizedlist>${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}</itemizedlist>";
 
-  # Remove invisible and internal options.
-  optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options);
-
   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 \
+    ${pkgs.python3Minimal}/bin/python ${./generateDoc.py} \
+      --format asciidoc \
+      ${optionsJSON}/share/doc/nixos/options.json \
       > $out
   '';
 
   optionsCommonMark = pkgs.runCommand "options.md" {} ''
-    ${pkgs.python3Minimal}/bin/python ${./generateCommonMark.py} \
-      < ${optionsJSON}/share/doc/nixos/options.json \
+    ${pkgs.python3Minimal}/bin/python ${./generateDoc.py} \
+      --format commonmark \
+      ${optionsJSON}/share/doc/nixos/options.json \
       > $out
   '';
 
   optionsJSON = pkgs.runCommand "options.json"
     { meta.description = "List of NixOS options in JSON format";
-      buildInputs = [ pkgs.brotli ];
+      nativeBuildInputs = [
+        pkgs.brotli
+        (let
+          # python3Minimal can't be overridden with packages on Darwin, due to a missing framework.
+          # Instead of modifying stdenv, we take the easy way out, since most people on Darwin will
+          # just be hacking on the Nixpkgs manual (which also uses make-options-doc).
+          python = if pkgs.stdenv.isDarwin then pkgs.python3 else pkgs.python3Minimal;
+          self = (python.override {
+            inherit self;
+            includeSiteCustomize = true;
+           });
+         in self.withPackages (p: [ p.mistune ]))
+      ];
       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"
-          else ''
-            ${pkgs.python3Minimal}/bin/python ${./mergeJSON.py} \
-              ${lib.optionalString warningsAreErrors "--warnings-are-errors"} \
-              ${baseOptionsJSON} $options \
-              > $dst/options.json
-          ''
-      }
+      python ${./mergeJSON.py} \
+        ${lib.optionalString warningsAreErrors "--warnings-are-errors"} \
+        ${lib.optionalString (! allowDocBook) "--error-on-docbook"} \
+        ${lib.optionalString markdownByDefault "--markdown-by-default"} \
+        $baseJSON $options \
+        > $dst/options.json
 
       brotli -9 < $dst/options.json > $dst/options.json.br
 
@@ -161,7 +174,10 @@ in rec {
 
     ${pkgs.python3Minimal}/bin/python ${./sortXML.py} $optionsXML sorted.xml
     ${pkgs.libxslt.bin}/bin/xsltproc \
+      --stringparam documentType '${documentType}' \
       --stringparam revision '${revision}' \
+      --stringparam variablelistId '${variablelistId}' \
+      --stringparam optionIdPrefix '${optionIdPrefix}' \
       -o intermediate.xml ${./options-to-docbook.xsl} sorted.xml
     ${pkgs.libxslt.bin}/bin/xsltproc \
       -o "$out" ${./postprocess-option-descriptions.xsl} intermediate.xml
diff --git a/nixos/lib/make-options-doc/generateAsciiDoc.py b/nixos/lib/make-options-doc/generateAsciiDoc.py
deleted file mode 100644
index 48eadd248c5a..000000000000
--- a/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/nixos/lib/make-options-doc/generateCommonMark.py b/nixos/lib/make-options-doc/generateCommonMark.py
deleted file mode 100644
index 404e53b0df9c..000000000000
--- a/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/nixos/lib/make-options-doc/generateDoc.py b/nixos/lib/make-options-doc/generateDoc.py
new file mode 100644
index 000000000000..1fe4eb0253ad
--- /dev/null
+++ b/nixos/lib/make-options-doc/generateDoc.py
@@ -0,0 +1,108 @@
+import argparse
+import json
+import sys
+
+formats = ['commonmark', 'asciidoc']
+
+parser = argparse.ArgumentParser(
+    description = 'Generate documentation for a set of JSON-formatted NixOS options'
+)
+parser.add_argument(
+    'nix_options_path',
+    help = 'a path to a JSON file containing the NixOS options'
+)
+parser.add_argument(
+    '-f',
+    '--format',
+    choices = formats,
+    required = True,
+    help = f'the documentation format to generate'
+)
+
+args = parser.parse_args()
+
+# Pretty-print certain Nix types, like literal expressions.
+def render_types(obj):
+    if '_type' not in obj: return obj
+
+    _type = obj['_type']
+    if _type == 'literalExpression' or _type == 'literalDocBook':
+        return obj['text']
+
+    if _type == 'derivation':
+        return obj['name']
+
+    raise Exception(f'Unexpected type `{_type}` in {json.dumps(obj)}')
+
+def generate_commonmark(options):
+    for (name, value) in options.items():
+        print('##', name.replace('<', '&lt;').replace('>', '&gt;'))
+        print(value['description'])
+        print()
+        if 'type' in value:
+            print('*_Type_*')
+            print ('```')
+            print(value['type'])
+            print ('```')
+        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()
+
+# TODO: declarations: link to github
+def generate_asciidoc(options):
+    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()
+
+with open(args.nix_options_path) as nix_options_json:
+    options = json.load(nix_options_json, object_hook=render_types)
+
+    if args.format == 'commonmark':
+        generate_commonmark(options)
+    elif args.format == 'asciidoc':
+        generate_asciidoc(options)
+    else:
+        raise Exception(f'Unsupported documentation format `--format {args.format}`')
+
diff --git a/nixos/lib/make-options-doc/mergeJSON.py b/nixos/lib/make-options-doc/mergeJSON.py
index 44a188a08c99..750cd24fc653 100644
--- a/nixos/lib/make-options-doc/mergeJSON.py
+++ b/nixos/lib/make-options-doc/mergeJSON.py
@@ -3,6 +3,11 @@ import json
 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:
@@ -41,8 +46,194 @@ def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]:
         result[opt.name] = opt.value
     return result
 
-warningsAreErrors = sys.argv[1] == "--warnings-are-errors"
-optOffset = 1 if warningsAreErrors else 0
+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 var(self, text):
+        return f"<varname>{escape(text)}</varname>"
+    def env(self, text):
+        return f"<envar>{escape(text)}</envar>"
+    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_var(md):
+    VAR_PATTERN = r'\{var\}`(.*?)`'
+    def parse(self, m, state):
+        return ('var', m.group(1))
+    md.inline.register_rule('var', VAR_PATTERN, parse)
+    md.inline.rules.append('var')
+
+def p_env(md):
+    ENV_PATTERN = r'\{env\}`(.*?)`'
+    def parse(self, m, state):
+        return ('env', m.group(1))
+    md.inline.register_rule('env', ENV_PATTERN, parse)
+    md.inline.rules.append('env')
+
+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_var, p_env, 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():
+        try:
+            if optionIs(option, 'description', 'mdDoc'):
+                option['description'] = convertString(name, option['description']['text'])
+            elif markdownByDefault:
+                option['description'] = convertString(name, option['description'])
+
+            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 }
+        except Exception as e:
+            raise Exception(f"Failed to render option {name}: {str(e)}")
+
+
+    return options
+
+warningsAreErrors = False
+errorOnDocbook = False
+markdownByDefault = False
+optOffset = 0
+for arg in sys.argv[1:]:
+    if arg == "--warnings-are-errors":
+        optOffset += 1
+        warningsAreErrors = True
+    if arg == "--error-on-docbook":
+        optOffset += 1
+        errorOnDocbook = True
+    if arg == "--markdown-by-default":
+        optOffset += 1
+        markdownByDefault = True
+
 options = pivot(json.load(open(sys.argv[1 + optOffset], 'r')))
 overrides = pivot(json.load(open(sys.argv[2 + optOffset], 'r')))
 
@@ -50,7 +241,7 @@ overrides = pivot(json.load(open(sys.argv[2 + optOffset], 'r')))
 for (k, v) in options.items():
     # The _module options are not declared in nixos/modules
     if v.value['loc'][0] != "_module":
-        v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}', v.value['declarations']))
+        v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}' if isinstance(s, str) else s, v.value['declarations']))
 
 # merge both descriptions
 for (k, v) in overrides.items():
@@ -70,9 +261,37 @@ 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
+hasDocBookErrors = False
 for (k, v) in options.items():
+    if errorOnDocbook:
+        if isinstance(v.value.get('description', {}), str):
+            hasErrors = True
+            hasDocBookErrors = True
+            print(
+                f"\x1b[1;31merror: option {v.name} description uses DocBook\x1b[0m",
+                file=sys.stderr)
+        elif is_docbook(v.value, 'defaultText'):
+            hasErrors = True
+            hasDocBookErrors = True
+            print(
+                f"\x1b[1;31merror: option {v.name} default uses DocBook\x1b[0m",
+                file=sys.stderr)
+        elif is_docbook(v.value, 'example'):
+            hasErrors = True
+            hasDocBookErrors = True
+            print(
+                f"\x1b[1;31merror: 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)
@@ -83,6 +302,22 @@ 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 hasDocBookErrors:
+    print("Explanation: The documentation contains descriptions, examples, or defaults written in DocBook. " +
+        "NixOS is in the process of migrating from DocBook to Markdown, and " +
+        "DocBook is disallowed for in-tree modules. To change your contribution to "+
+        "use Markdown, apply mdDoc and literalMD. 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'');",
+        file = sys.stderr)
+
+if hasErrors:
+    sys.exit(1)
 if hasWarnings and warningsAreErrors:
     print(
         "\x1b[1;31m" +
@@ -92,4 +327,4 @@ if hasWarnings and warningsAreErrors:
         file=sys.stderr)
     sys.exit(1)
 
-json.dump(unpivot(options), fp=sys.stdout)
+json.dump(convertMD(unpivot(options)), fp=sys.stdout)
diff --git a/nixos/lib/make-options-doc/options-to-docbook.xsl b/nixos/lib/make-options-doc/options-to-docbook.xsl
index b286f7b5e2c0..ac49659c681f 100644
--- a/nixos/lib/make-options-doc/options-to-docbook.xsl
+++ b/nixos/lib/make-options-doc/options-to-docbook.xsl
@@ -12,15 +12,37 @@
   <xsl:output method='xml' encoding="UTF-8" />
 
   <xsl:param name="revision" />
+  <xsl:param name="documentType" />
   <xsl:param name="program" />
+  <xsl:param name="variablelistId" />
+  <xsl:param name="optionIdPrefix" />
 
 
   <xsl:template match="/expr/list">
-    <appendix xml:id="appendix-configuration-options">
-      <title>Configuration Options</title>
-      <variablelist xml:id="configuration-variable-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-', str:replace(str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '&lt;', '_'), '>', '_'), ':', '_'))" />
+          <xsl:variable name="id" select="
+            concat($optionIdPrefix,
+              translate(
+                attr[@name = 'name']/string/@value,
+                '*&lt; >[]:&quot;',
+                '________'
+            ))" />
           <varlistentry>
             <term xlink:href="#{$id}">
               <xsl:attribute name="xml:id"><xsl:value-of select="$id"/></xsl:attribute>
@@ -96,7 +118,6 @@
         </xsl:for-each>
 
       </variablelist>
-    </appendix>
   </xsl:template>
 
 
@@ -117,84 +138,25 @@
   </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
@@ -237,10 +199,4 @@
     </simplelist>
   </xsl:template>
 
-
-  <xsl:template match="function">
-    <xsl:text>λ</xsl:text>
-  </xsl:template>
-
-
 </xsl:stylesheet>
diff --git a/nixos/lib/make-single-disk-zfs-image.nix b/nixos/lib/make-single-disk-zfs-image.nix
index 9310febd9179..ef3b1c0e2842 100644
--- a/nixos/lib/make-single-disk-zfs-image.nix
+++ b/nixos/lib/make-single-disk-zfs-image.nix
@@ -116,7 +116,7 @@ let
       gptfdisk
       nix
       parted
-      utillinux
+      util-linux
       zfs
     ]
   );
@@ -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;
diff --git a/nixos/lib/make-system-tarball.nix b/nixos/lib/make-system-tarball.nix
index dab168f4a481..325792f97e8f 100644
--- a/nixos/lib/make-system-tarball.nix
+++ b/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/nixos/lib/qemu-common.nix b/nixos/lib/qemu-common.nix
index 250f714be0a7..a8ed27dd6091 100644
--- a/nixos/lib/qemu-common.nix
+++ b/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; isAarch32 || isAarch64 || 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/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index 16ec47df3a46..c6c8753d5325 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/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)";
@@ -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}
@@ -285,6 +298,8 @@ in rec {
           Documentation = toString config.documentation; }
         // optionalAttrs (config.onFailure != []) {
           OnFailure = toString config.onFailure; }
+        // optionalAttrs (config.onSuccess != []) {
+          OnSuccess = toString config.onSuccess; }
         // optionalAttrs (options.startLimitIntervalSec.isDefined) {
           StartLimitIntervalSec = toString config.startLimitIntervalSec;
         } // optionalAttrs (options.startLimitBurst.isDefined) {
@@ -338,7 +353,7 @@ in rec {
     '';
 
   targetToUnit = name: def:
-    { inherit (def) aliases wantedBy requiredBy enable;
+    { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
       text =
         ''
           [Unit]
@@ -347,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]
@@ -369,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]
@@ -380,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]
@@ -389,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]
@@ -398,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]
@@ -407,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]
@@ -416,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/nixos/lib/systemd-types.nix b/nixos/lib/systemd-types.nix
index 71962fab2e17..a109f248b170 100644
--- a/nixos/lib/systemd-types.nix
+++ b/nixos/lib/systemd-types.nix
@@ -1,4 +1,4 @@
-{ lib, systemdUtils }:
+{ lib, systemdUtils, pkgs }:
 
 with systemdUtils.lib;
 with systemdUtils.unitOptions;
@@ -34,4 +34,36 @@ rec {
 
   automounts = with types; listOf (submodule [ stage2AutomountOptions unitConfig automountConfig ]);
   initrdAutomounts = with types; attrsOf (submodule [ stage1AutomountOptions unitConfig automountConfig ]);
+
+  initrdContents = types.attrsOf (types.submodule ({ config, options, name, ... }: {
+    options = {
+      enable = mkEnableOption (lib.mdDoc "copying of this file and symlinking it") // { default = true; };
+
+      target = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          Path of the symlink.
+        '';
+        default = name;
+      };
+
+      text = mkOption {
+        default = null;
+        type = types.nullOr types.lines;
+        description = lib.mdDoc "Text of the file.";
+      };
+
+      source = mkOption {
+        type = types.path;
+        description = lib.mdDoc "Path of the source file.";
+      };
+    };
+
+    config = {
+      source = mkIf (config.text != null) (
+        let name' = "initrd-" + baseNameOf name;
+        in mkDerivedConfig options.text (pkgs.writeText name')
+      );
+    };
+  }));
 }
diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix
index 02e91fdf1e62..9c7cb34f14b5 100644
--- a/nixos/lib/systemd-unit-options.nix
+++ b/nixos/lib/systemd-unit-options.nix
@@ -20,10 +20,15 @@ in rec {
     merge = loc: defs:
       let
         defs' = filterOverrides defs;
-        defs'' = getValues defs';
       in
-        if isList (head defs'')
-        then concatLists defs''
+        if isList (head defs').value
+        then concatMap (def:
+          if builtins.typeOf def.value == "list"
+          then def.value
+          else
+            throw "The definitions for systemd unit options should be either all lists, representing repeatable options, or all non-lists, but for the option ${showOption loc}, the definitions are a mix of list and non-list ${lib.options.showDefs defs'}"
+        ) defs'
+
         else mergeEqualOption loc defs';
   };
 
@@ -32,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 systemd.unit(1).
       '';
     };
 
     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 in systemd.unit(5) manpage described `[Install]` section 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.";
     };
 
   };
@@ -84,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.";
     };
 
   };
@@ -100,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.
         '';
@@ -121,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.
         '';
       };
@@ -129,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.
         '';
@@ -138,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.
         '';
@@ -147,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.
         '';
@@ -156,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.
         '';
@@ -165,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.
         '';
@@ -174,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.
         '';
@@ -184,26 +205,34 @@ 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.
         '';
       };
 
+      onSuccess = mkOption {
+        default = [];
+        type = types.listOf unitNameType;
+        description = lib.mdDoc ''
+          A list of one or more units that are activated when
+          this unit enters the "inactive" state.
+        '';
+      };
+
       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.
@@ -212,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.
@@ -231,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.
@@ -241,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
@@ -259,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.
         '';
       };
@@ -279,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.
         '';
@@ -311,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.
         '';
@@ -320,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.
         '';
@@ -329,7 +361,7 @@ in rec {
       preStop = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed to stop the service.
         '';
       };
@@ -337,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.
         '';
@@ -346,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 = [];
       };
 
@@ -391,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.
         '';
@@ -400,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.
         '';
@@ -416,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.
         '';
       };
@@ -432,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 ];
       };
@@ -460,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.
         '';
       };
 
@@ -470,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.
         '';
       };
 
@@ -480,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.
         '';
       };
     };
@@ -513,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.
         '';
       };
 
@@ -548,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.
         '';
       };
 
@@ -580,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)
         '';
@@ -596,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.
         '';
       };
 
@@ -641,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)
         '';
@@ -651,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.
         '';
       };
 
@@ -683,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/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix
index 3aee91343189..e3786622c3c5 100644
--- a/nixos/lib/test-driver/default.nix
+++ b/nixos/lib/test-driver/default.nix
@@ -10,6 +10,7 @@
 , socat
 , tesseract4
 , vde2
+, extraPythonPackages ? (_ : [])
 }:
 
 python3Packages.buildPythonApplication rec {
@@ -17,14 +18,25 @@ python3Packages.buildPythonApplication rec {
   version = "1.1";
   src = ./.;
 
-  propagatedBuildInputs = [ coreutils netpbm python3Packages.colorama python3Packages.ptpython qemu_pkg socat vde2 ]
-    ++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ]);
+  propagatedBuildInputs = [
+    coreutils
+    netpbm
+    python3Packages.colorama
+    python3Packages.ptpython
+    qemu_pkg
+    socat
+    vde2
+  ]
+    ++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ])
+    ++ extraPythonPackages python3Packages;
 
   doCheck = true;
   checkInputs = with python3Packages; [ mypy pylint black ];
   checkPhase = ''
     mypy --disallow-untyped-defs \
           --no-implicit-optional \
+          --pretty \
+          --no-color-output \
           --ignore-missing-imports ${src}/test_driver
     pylint --errors-only --enable=unused-import ${src}/test_driver
     black --check --diff ${src}/test_driver
diff --git a/nixos/lib/test-driver/setup.py b/nixos/lib/test-driver/setup.py
index 476c7b2dab2a..1719b988db68 100644
--- a/nixos/lib/test-driver/setup.py
+++ b/nixos/lib/test-driver/setup.py
@@ -4,6 +4,7 @@ setup(
   name="nixos-test-driver",
   version='1.1',
   packages=find_packages(),
+  package_data={"test_driver": ["py.typed"]},
   entry_points={
     "console_scripts": [
       "nixos-test-driver=test_driver:main",
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 035e3ffe8973..ffbc7c18e42b 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -426,7 +426,9 @@ class Machine:
             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.
@@ -456,7 +458,7 @@ class Machine:
                 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)
@@ -682,10 +684,10 @@ class Machine:
         with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)):
             retry(tty_matches)
 
-    def send_chars(self, chars: List[str]) -> None:
+    def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None:
         with self.nested("sending keys ‘{}‘".format(chars)):
             for char in chars:
-                self.send_key(char)
+                self.send_key(char, delay)
 
     def wait_for_file(self, filename: str) -> None:
         """Waits until the file exists in machine's file system."""
@@ -710,7 +712,7 @@ class Machine:
             status, _ = self.execute("nc -z localhost {}".format(port))
             return status != 0
 
-        with self.nested("waiting for TCP port {} to be closed"):
+        with self.nested("waiting for TCP port {} to be closed".format(port)):
             retry(port_is_closed)
 
     def start_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
@@ -858,10 +860,11 @@ class Machine:
                 if matches is not None:
                     return
 
-    def send_key(self, key: str) -> None:
+    def send_key(self, key: str, delay: Optional[float] = 0.01) -> None:
         key = CHAR_TO_KEY.get(key, key)
         self.send_monitor_command("sendkey {}".format(key))
-        time.sleep(0.01)
+        if delay is not None:
+            time.sleep(delay)
 
     def send_console(self, chars: str) -> None:
         assert self.process
diff --git a/nixos/lib/test-driver/test_driver/py.typed b/nixos/lib/test-driver/test_driver/py.typed
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/nixos/lib/test-driver/test_driver/py.typed
diff --git a/nixos/lib/test-driver/test_driver/vlan.py b/nixos/lib/test-driver/test_driver/vlan.py
index e5c8f07b4edf..f2a7f250d1d2 100644
--- a/nixos/lib/test-driver/test_driver/vlan.py
+++ b/nixos/lib/test-driver/test_driver/vlan.py
@@ -32,8 +32,12 @@ class VLan:
         rootlog.info("start vlan")
         pty_master, pty_slave = pty.openpty()
 
+        # The --hub is required for the scenario determined by
+        # nixos/tests/networking.nix vlan-ping.
+        # VLAN Tagged traffic (802.1Q) seams to be blocked if a vde_switch is
+        # used without the hub mode (flood packets to all ports).
         self.process = subprocess.Popen(
-            ["vde_switch", "-s", self.socket_dir, "--dirmode", "0700"],
+            ["vde_switch", "-s", self.socket_dir, "--dirmode", "0700", "--hub"],
             stdin=pty_slave,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
@@ -50,7 +54,7 @@ class VLan:
         if not (self.socket_dir / "ctl").exists():
             rootlog.error("cannot start vde_switch")
 
-        rootlog.info(f"running vlan (pid {self.pid})")
+        rootlog.info(f"running vlan (pid {self.pid}; ctl {self.socket_dir})")
 
     def __del__(self) -> None:
         rootlog.info(f"kill vlan (pid {self.pid})")
diff --git a/nixos/lib/test-script-prepend.py b/nixos/lib/test-script-prepend.py
new file mode 100644
index 000000000000..15e59ce01047
--- /dev/null
+++ b/nixos/lib/test-script-prepend.py
@@ -0,0 +1,42 @@
+# This file contains type hints that can be prepended to Nix test scripts so they can be type
+# checked.
+
+from test_driver.driver import Driver
+from test_driver.vlan import VLan
+from test_driver.machine import Machine
+from test_driver.logger import Logger
+from typing import Callable, Iterator, ContextManager, Optional, List, Dict, Any, Union
+from typing_extensions import Protocol
+from pathlib import Path
+
+
+class RetryProtocol(Protocol):
+    def __call__(self, fn: Callable, timeout: int = 900) -> None:
+        raise Exception("This is just type information for the Nix test driver")
+
+
+class PollingConditionProtocol(Protocol):
+    def __call__(
+        self,
+        fun_: Optional[Callable] = None,
+        *,
+        seconds_interval: float = 2.0,
+        description: Optional[str] = None,
+    ) -> Union[Callable[[Callable], ContextManager], ContextManager]:
+        raise Exception("This is just type information for the Nix test driver")
+
+
+start_all: Callable[[], None]
+subtest: Callable[[str], ContextManager[None]]
+retry: RetryProtocol
+test_script: Callable[[], None]
+machines: List[Machine]
+vlans: List[VLan]
+driver: Driver
+log: Logger
+create_machine: Callable[[Dict[str, Any]], Machine]
+run_tests: Callable[[], None]
+join_all: Callable[[], None]
+serial_stdout_off: Callable[[], None]
+serial_stdout_on: Callable[[], None]
+polling_condition: PollingConditionProtocol
diff --git a/nixos/lib/testing-python.nix b/nixos/lib/testing-python.nix
index cd2bb2f9d4d4..134d38f1b676 100644
--- a/nixos/lib/testing-python.nix
+++ b/nixos/lib/testing-python.nix
@@ -9,148 +9,33 @@
   # Modules to add to each VM
 , extraConfigurations ? [ ]
 }:
-
-with pkgs;
+let
+  nixos-lib = import ./default.nix { inherit (pkgs) lib; };
+in
 
 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
-    , passthru ? {}
-    , interactive ? false
-  }:
-    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;
-        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) "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;
-
-    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 ];
-        testScript = testScript';
-        preferLocalBuild = true;
-        passthru = passthru // {
-          inherit nodes;
-        };
-      }
-      ''
-        mkdir -p $out/bin
-
-        vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
-        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 ? {}
     , testScript
     , enableOCR ? false
     , name ? "unnamed"
+    , skipTypeCheck ? false
       # Skip linting (mainly intended for faster dev cycles)
     , skipLint ? false
     , passthru ? {}
@@ -160,90 +45,24 @@ rec {
         (if meta.description or null != null
           then builtins.unsafeGetAttrPos "description" meta
           else builtins.unsafeGetAttrPos "testScript" t)
-    } @ 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-pyton.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 skipLint passthru;
-        testName = name;
-        qemu_pkg = pkgs.qemu_test;
-        nodes = mkNodes pkgs.qemu_test;
-      };
-      driverInteractive = setupDriverForTest {
-        inherit testScript enableOCR skipLint passthru;
-        testName = name;
-        qemu_pkg = pkgs.qemu;
-        nodes = mkNodes pkgs.qemu;
-        interactive = true;
-      };
-
-      test = lib.addMetaAttrs meta (runTests { inherit driver pos driverInteractive; });
-
+    , extraPythonPackages ? (_ : [])
+    , 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/nixos/lib/testing/call-test.nix b/nixos/lib/testing/call-test.nix
new file mode 100644
index 000000000000..9abcea07455e
--- /dev/null
+++ b/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/nixos/lib/testing/default.nix b/nixos/lib/testing/default.nix
new file mode 100644
index 000000000000..9d4f9dbc43d7
--- /dev/null
+++ b/nixos/lib/testing/default.nix
@@ -0,0 +1,24 @@
+{ lib }:
+let
+
+  evalTest = module: lib.evalModules { modules = testModules ++ [ module ]; };
+  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/nixos/lib/testing/driver.nix b/nixos/lib/testing/driver.nix
new file mode 100644
index 000000000000..fb181c1d7e9a
--- /dev/null
+++ b/nixos/lib/testing/driver.nix
@@ -0,0 +1,188 @@
+{ 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.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";
+
+  # 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;
+
+  uniqueVlans = lib.unique (builtins.concatLists vlans);
+  vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
+  machineNames = map (name: "${name}: Machine;") nodeHostNames;
+
+  withChecks =
+    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 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 "," nodeHostNames)},
+            < ${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/nixos/lib/testing/interactive.nix b/nixos/lib/testing/interactive.nix
new file mode 100644
index 000000000000..317ed4241882
--- /dev/null
+++ b/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/nixos/lib/testing/legacy.nix b/nixos/lib/testing/legacy.nix
new file mode 100644
index 000000000000..868b8b65b17d
--- /dev/null
+++ b/nixos/lib/testing/legacy.nix
@@ -0,0 +1,25 @@
+{ config, options, lib, ... }:
+let
+  inherit (lib) mkIf mkOption types;
+in
+{
+  # This needs options.warnings, which we don't have (yet?).
+  # imports = [
+  #   (lib.mkRenamedOptionModule [ "machine" ] [ "nodes" "machine" ])
+  # ];
+
+  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/nixos/lib/testing/meta.nix b/nixos/lib/testing/meta.nix
new file mode 100644
index 000000000000..65754fe3c541
--- /dev/null
+++ b/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 = null; # NOTE: null values are filtered out by `meta`.
+            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/nixos/lib/testing/name.nix b/nixos/lib/testing/name.nix
new file mode 100644
index 000000000000..0af593169eec
--- /dev/null
+++ b/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/nixos/lib/testing/network.nix b/nixos/lib/testing/network.nix
new file mode 100644
index 000000000000..04ea9a2bc9f7
--- /dev/null
+++ b/nixos/lib/testing/network.nix
@@ -0,0 +1,117 @@
+{ lib, nodes, ... }:
+
+let
+  inherit (lib)
+    attrNames concatMap concatMapStrings flip forEach head
+    listToAttrs mkDefault mkOption nameValuePair optionalString
+    range types zipListsWith zipLists
+    mdDoc
+    ;
+
+  nodeNumbers =
+    listToAttrs
+      (zipListsWith
+        nameValuePair
+        (attrNames nodes)
+        (range 1 254)
+      );
+
+  networkModule = { config, nodes, pkgs, ... }:
+    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 config.virtualisation.test.nodeNumber}";
+              prefixLength = 24;
+            }];
+        });
+
+      networkConfig =
+        {
+          networking.hostName = mkDefault config.virtualisation.test.nodeName;
+
+          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 (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 =
+            let qemu-common = import ../qemu-common.nix { inherit lib pkgs; };
+            in
+            flip concatMap interfacesNumbered
+              ({ fst, snd }: qemu-common.qemuNICFlags snd fst config.virtualisation.test.nodeNumber);
+        };
+
+    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;
+      };
+    };
+
+  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/nixos/lib/testing/nixos-test-base.nix b/nixos/lib/testing/nixos-test-base.nix
new file mode 100644
index 000000000000..59e6e3843367
--- /dev/null
+++ b/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/nixos/lib/testing/nodes.nix b/nixos/lib/testing/nodes.nix
new file mode 100644
index 000000000000..8e620c96b3bb
--- /dev/null
+++ b/nixos/lib/testing/nodes.nix
@@ -0,0 +1,112 @@
+testModuleArgs@{ config, lib, hostPkgs, nodes, ... }:
+
+let
+  inherit (lib) mkOption mkForce optional types mapAttrs mkDefault mdDoc;
+
+  system = hostPkgs.stdenv.hostPlatform.system;
+
+  baseOS =
+    import ../eval-config.nix {
+      inherit 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;
+
+              # 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;
+            })
+          testModuleArgs.config.extraBaseModules
+        ] ++ optional config.minimal ../../modules/testing/minimal-kernel.nix;
+    };
+
+
+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.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.
+      '';
+    };
+
+    minimal = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Enable to configure all [{option}`nodes`](#test-opt-nodes) to run with a minimal kernel.
+      '';
+    };
+
+    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;
+  };
+}
diff --git a/nixos/lib/testing/pkgs.nix b/nixos/lib/testing/pkgs.nix
new file mode 100644
index 000000000000..22dd586868e3
--- /dev/null
+++ b/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/nixos/lib/testing/run.nix b/nixos/lib/testing/run.nix
new file mode 100644
index 000000000000..0cd07d8afd21
--- /dev/null
+++ b/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/nixos/lib/testing/testScript.nix b/nixos/lib/testing/testScript.nix
new file mode 100644
index 000000000000..5d4181c5f5dd
--- /dev/null
+++ b/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 = ''
+        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/nixos/lib/utils.nix b/nixos/lib/utils.nix
index 497d98aa4d19..def3aa13f320 100644
--- a/nixos/lib/utils.nix
+++ b/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
@@ -213,6 +225,6 @@ rec {
   systemdUtils = {
     lib = import ./systemd-lib.nix { inherit lib config pkgs; };
     unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils; };
-    types = import ./systemd-types.nix { inherit lib systemdUtils; };
+    types = import ./systemd-types.nix { inherit lib systemdUtils pkgs; };
   };
 }
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index 2d89db0a7f34..0db0f4d0dcca 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -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 = 2048;
       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;
           ''}
@@ -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;
 
diff --git a/nixos/maintainers/scripts/ec2/create-amis.sh b/nixos/maintainers/scripts/ec2/create-amis.sh
index 797fe03e2095..0c1656efaf1c 100755
--- a/nixos/maintainers/scripts/ec2/create-amis.sh
+++ b/nixos/maintainers/scripts/ec2/create-amis.sh
@@ -26,12 +26,32 @@ var ${home_region:=eu-west-1}
 var ${bucket:=nixos-amis}
 var ${service_role_name:=vmimport}
 
-var ${regions:=eu-west-1 eu-west-2 eu-west-3 eu-central-1 eu-north-1
-         us-east-1 us-east-2 us-west-1 us-west-2
+# Output of the command:
+# > aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text | sort
+var ${regions:=
+         af-south-1
+         ap-east-1
+         ap-northeast-1
+         ap-northeast-2
+         ap-northeast-3
+         ap-south-1
+         ap-southeast-1
+         ap-southeast-2
+         ap-southeast-3
          ca-central-1
-         ap-southeast-1 ap-southeast-2 ap-northeast-1 ap-northeast-2
-         ap-south-1 ap-east-1
-         sa-east-1}
+         eu-central-1
+         eu-north-1
+         eu-south-1
+         eu-west-1
+         eu-west-2
+         eu-west-3
+         me-south-1
+         sa-east-1
+         us-east-1
+         us-east-2
+         us-west-1
+         us-west-2
+     }
 
 regions=($regions)
 
diff --git a/nixos/maintainers/scripts/lxd/lxd-image-inner.nix b/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
index 74634fd1671c..c8cf2a04fb10 100644
--- a/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-image-inner.nix
@@ -55,7 +55,7 @@ with lib;
   # services.xserver.libinput.enable = true;
 
   # Define a user account. Don't forget to set a password with ‘passwd’.
-  # users.users.jane = {
+  # users.users.alice = {
   #   isNormalUser = true;
   #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
   # };
@@ -94,9 +94,4 @@ with lib;
   # Before changing this value read the documentation for this option
   # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
   system.stateVersion = "21.05"; # Did you read the comment?
-
-  # As this is intended as a stadalone image, undo some of the minimal profile stuff
-  documentation.enable = true;
-  documentation.nixos.enable = true;
-  environment.noXlibs = false;
 }
diff --git a/nixos/maintainers/scripts/lxd/lxd-image.nix b/nixos/maintainers/scripts/lxd/lxd-image.nix
index c76b9fcc7f77..cf30836dffe5 100644
--- a/nixos/maintainers/scripts/lxd/lxd-image.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-image.nix
@@ -26,9 +26,4 @@ with lib;
   # Network
   networking.useDHCP = false;
   networking.interfaces.eth0.useDHCP = true;
-
-  # As this is intended as a stadalone image, undo some of the minimal profile stuff
-  documentation.enable = true;
-  documentation.nixos.enable = true;
-  environment.noXlibs = false;
 }
diff --git a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
index d62a560642d0..f73e251d3046 100644
--- a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
+++ b/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";
     };
   };
 
diff --git a/nixos/modules/config/appstream.nix b/nixos/modules/config/appstream.nix
index a72215c2f561..5b48f6e1705d 100644
--- a/nixos/modules/config/appstream.nix
+++ b/nixos/modules/config/appstream.nix
@@ -6,9 +6,9 @@ with lib;
     appstream.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://www.freedesktop.org/software/appstream/docs/index.html">AppStream metadata specification</link>.
+        [AppStream metadata specification](https://www.freedesktop.org/software/appstream/docs/index.html).
       '';
     };
   };
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index 5b07901f9901..f5db5dc5dfc1 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -34,21 +34,23 @@ 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";
       example = "LatArCyrHeb-16";
-      description = ''
+      description = mdDoc ''
         The font used for the virtual consoles.  Leave empty to use
-        whatever the <command>setfont</command> program considers the
+        whatever the {command}`setfont` program considers the
         default font.
         Can be either a font name or a path to a PSF font file.
       '';
@@ -58,21 +60,21 @@ in
       type = with types; either str path;
       default = "us";
       example = "fr";
-      description = ''
+      description = lib.mdDoc ''
         The keyboard mapping table for the virtual consoles.
       '';
     };
 
     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"
         "002b36" "cb4b16" "586e75" "657b83"
         "839496" "6c71c4" "93a1a1" "fdf6e3"
       ];
-      description = ''
+      description = lib.mdDoc ''
         The 16 colors palette used by the virtual consoles.
         Leave empty to use the default colors.
         Colors must be in hexadecimal format and listed in
@@ -84,7 +86,7 @@ in
     packages = mkOption {
       type = types.listOf types.package;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         List of additional packages that provide console fonts, keymaps and
         other resources for virtual consoles use.
       '';
@@ -93,7 +95,7 @@ in
     useXkbConfig = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If set, configure the virtual console keymap from the xserver
         keyboard settings.
       '';
@@ -102,7 +104,7 @@ in
     earlySetup = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Enable setting virtual console options as early as possible (in initrd).
       '';
     };
@@ -125,11 +127,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 = [ pkgs.kbd ];
 
         # Let systemd-vconsole-setup.service do the work of setting up the
@@ -149,14 +157,21 @@ in
         '');
 
         boot.initrd.systemd.contents = {
-          "/etc/kbd".source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share";
           "/etc/vconsole.conf".source = vconsoleConf;
+          # Add everything if we want full console setup...
+          "/etc/kbd" = lib.mkIf cfg.earlySetup { source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share"; };
+          # ...but only the keymaps if we don't
+          "/etc/kbd/keymaps" = lib.mkIf (!cfg.earlySetup) { source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/keymaps"; };
         };
         boot.initrd.systemd.storePaths = [
           "${config.boot.initrd.systemd.package}/lib/systemd/systemd-vconsole-setup"
           "${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" # keyboard layouts are compressed
+          "${config.boot.initrd.systemd.package.kbd.gzip}/bin/gzip" # Fonts and keyboard layouts are compressed
+        ] ++ optionals (hasPrefix builtins.storeDir cfg.font) [
+          "${cfg.font}"
+        ] ++ optionals (hasPrefix builtins.storeDir cfg.keyMap) [
+          "${cfg.keyMap}"
         ];
 
         systemd.services.reload-systemd-vconsole-setup =
@@ -180,7 +195,7 @@ in
         ];
       })
 
-      (mkIf cfg.earlySetup {
+      (mkIf (cfg.earlySetup && !config.boot.initrd.systemd.enable) {
         boot.initrd.extraUtilsCommands = ''
           mkdir -p $out/share/consolefonts
           ${if substring 0 1 cfg.font == "/" then ''
@@ -194,10 +209,6 @@ in
             cp -L $font $out/share/consolefonts/font.psf
           fi
         '';
-        assertions = [{
-          assertion = !config.boot.initrd.systemd.enable;
-          message = "console.earlySetup is implied by systemd stage 1";
-        }];
       })
     ]))
   ];
diff --git a/nixos/modules/config/debug-info.nix b/nixos/modules/config/debug-info.nix
index 2942ae5905d1..78de26fda440 100644
--- a/nixos/modules/config/debug-info.nix
+++ b/nixos/modules/config/debug-info.nix
@@ -9,21 +9,20 @@ with lib;
     environment.enableDebugInfo = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = mdDoc ''
         Some NixOS packages provide debug symbols. However, these are
         not included in the system closure by default to save disk
         space. Enabling this option causes the debug symbols to appear
-        in <filename>/run/current-system/sw/lib/debug/.build-id</filename>,
-        where tools such as <command>gdb</command> can find them.
+        in {file}`/run/current-system/sw/lib/debug/.build-id`,
+        where tools such as {command}`gdb` can find them.
         If you need debug symbols for a package that doesn't
         provide them by default, you can enable them as follows:
-        <programlisting>
-        nixpkgs.config.packageOverrides = pkgs: {
-          hello = pkgs.hello.overrideAttrs (oldAttrs: {
-            separateDebugInfo = true;
-          });
-        };
-        </programlisting>
+
+            nixpkgs.config.packageOverrides = pkgs: {
+              hello = pkgs.hello.overrideAttrs (oldAttrs: {
+                separateDebugInfo = true;
+              });
+            };
       '';
     };
 
diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
index 1e68fef7ce74..f9c6e5be226b 100644
--- a/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixos/modules/config/fonts/fontconfig.nix
@@ -65,7 +65,7 @@ let
           ${fcBool cfg.hinting.autohint}
         </edit>
         <edit mode="append" name="hintstyle">
-          <const>hintslight</const>
+          <const>${cfg.hinting.style}</const>
         </edit>
         <edit mode="append" name="antialias">
           ${fcBool cfg.antialias}
@@ -226,7 +226,6 @@ in
     (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ])
     (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ])
     (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ])
-    (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
     (mkRemovedOptionModule [ "fonts" "fontconfig" "dpi" ] "Use display server-specific options")
@@ -247,7 +246,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             If enabled, a Fontconfig configuration file will be built
             pointing to a set of default fonts.  If you don't care about
             running X11 applications or any other program that uses
@@ -260,7 +259,7 @@ in
           internal = true;
           type     = with types; listOf path;
           default  = [ ];
-          description = ''
+          description = lib.mdDoc ''
             Fontconfig configuration packages.
           '';
         };
@@ -268,7 +267,7 @@ in
         antialias = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Enable font antialiasing. At high resolution (> 200 DPI),
             antialiasing has no visible effect; users of such displays may want
             to disable this option.
@@ -278,9 +277,9 @@ in
         localConf = mkOption {
           type = types.lines;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             System-wide customization file contents, has higher priority than
-            <literal>defaultFonts</literal> settings.
+            `defaultFonts` settings.
           '';
         };
 
@@ -288,7 +287,7 @@ in
           monospace = mkOption {
             type = types.listOf types.str;
             default = ["DejaVu Sans Mono"];
-            description = ''
+            description = lib.mdDoc ''
               System-wide default monospace font(s). Multiple fonts may be
               listed in case multiple languages must be supported.
             '';
@@ -297,7 +296,7 @@ in
           sansSerif = mkOption {
             type = types.listOf types.str;
             default = ["DejaVu Sans"];
-            description = ''
+            description = lib.mdDoc ''
               System-wide default sans serif font(s). Multiple fonts may be
               listed in case multiple languages must be supported.
             '';
@@ -306,7 +305,7 @@ in
           serif = mkOption {
             type = types.listOf types.str;
             default = ["DejaVu Serif"];
-            description = ''
+            description = lib.mdDoc ''
               System-wide default serif font(s). Multiple fonts may be listed
               in case multiple languages must be supported.
             '';
@@ -315,7 +314,7 @@ in
           emoji = mkOption {
             type = types.listOf types.str;
             default = ["Noto Color Emoji"];
-            description = ''
+            description = lib.mdDoc ''
               System-wide default emoji font(s). Multiple fonts may be listed
               in case a font does not support all emoji.
 
@@ -332,7 +331,7 @@ in
           enable = mkOption {
             type = types.bool;
             default = true;
-            description = ''
+            description = lib.mdDoc ''
               Enable font hinting. Hinting aligns glyphs to pixel boundaries to
               improve rendering sharpness at low resolution. At high resolution
               (> 200 dpi) hinting will do nothing (at best); users of such
@@ -343,21 +342,35 @@ in
           autohint = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Enable the autohinter in place of the default interpreter.
               The results are usually lower quality than correctly-hinted
               fonts, but better than unhinted fonts.
             '';
           };
+
+          style = mkOption {
+            type = types.enum [ "hintnone" "hintslight" "hintmedium" "hintfull" ];
+            default = "hintslight";
+            description = lib.mdDoc ''
+              Hintstyle is the amount of font reshaping done to line up
+              to the grid.
+
+              hintslight will make the font more fuzzy to line up to the grid
+              but will be better in retaining font shape, while hintfull will
+              be a crisp font that aligns well to the pixel grid but will lose
+              a greater amount of font shape.
+            '';
+          };
         };
 
         includeUserConf = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Include the user configuration from
-            <filename>~/.config/fontconfig/fonts.conf</filename> or
-            <filename>~/.config/fontconfig/conf.d</filename>.
+            {file}`~/.config/fontconfig/fonts.conf` or
+            {file}`~/.config/fontconfig/conf.d`.
           '';
         };
 
@@ -366,26 +379,26 @@ in
           rgba = mkOption {
             default = "rgb";
             type = types.enum ["rgb" "bgr" "vrgb" "vbgr" "none"];
-            description = ''
+            description = lib.mdDoc ''
               Subpixel order. The overwhelming majority of displays are
-              <literal>rgb</literal> in their normal orientation. Select
-              <literal>vrgb</literal> for mounting such a display 90 degrees
-              clockwise from its normal orientation or <literal>vbgr</literal>
+              `rgb` in their normal orientation. Select
+              `vrgb` for mounting such a display 90 degrees
+              clockwise from its normal orientation or `vbgr`
               for mounting 90 degrees counter-clockwise. Select
-              <literal>bgr</literal> in the unlikely event of mounting 180
+              `bgr` in the unlikely event of mounting 180
               degrees from the normal orientation. Reverse these directions in
               the improbable event that the display's native subpixel order is
-              <literal>bgr</literal>.
+              `bgr`.
             '';
           };
 
           lcdfilter = mkOption {
             default = "default";
             type = types.enum ["none" "default" "light" "legacy"];
-            description = ''
+            description = lib.mdDoc ''
               FreeType LCD filter. At high resolution (> 200 DPI), LCD filtering
               has no visible effect; users of such displays may want to select
-              <literal>none</literal>.
+              `none`.
             '';
           };
 
@@ -394,7 +407,7 @@ in
         cache32Bit = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Generate system fonts cache for 32-bit applications.
           '';
         };
@@ -402,8 +415,8 @@ in
         allowBitmaps = mkOption {
           type = types.bool;
           default = true;
-          description = ''
-            Allow bitmap fonts. Set to <literal>false</literal> to ban all
+          description = lib.mdDoc ''
+            Allow bitmap fonts. Set to `false` to ban all
             bitmap fonts.
           '';
         };
@@ -411,8 +424,8 @@ in
         allowType1 = mkOption {
           type = types.bool;
           default = false;
-          description = ''
-            Allow Type-1 fonts. Default is <literal>false</literal> because of
+          description = lib.mdDoc ''
+            Allow Type-1 fonts. Default is `false` because of
             poor rendering.
           '';
         };
@@ -420,7 +433,7 @@ in
         useEmbeddedBitmaps = mkOption {
           type = types.bool;
           default = false;
-          description = "Use embedded bitmaps in fonts like Calibri.";
+          description = lib.mdDoc "Use embedded bitmaps in fonts like Calibri.";
         };
 
       };
diff --git a/nixos/modules/config/fonts/fontdir.nix b/nixos/modules/config/fonts/fontdir.nix
index 560918302ca6..30e0dfe2566a 100644
--- a/nixos/modules/config/fonts/fontdir.nix
+++ b/nixos/modules/config/fonts/fontdir.nix
@@ -30,9 +30,9 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create a directory with links to all fonts in
-          <filename>/run/current-system/sw/share/X11/fonts</filename>.
+          {file}`/run/current-system/sw/share/X11/fonts`.
         '';
       };
 
@@ -40,9 +40,9 @@ in
         type = types.bool;
         default = config.programs.xwayland.enable;
         defaultText = literalExpression "config.programs.xwayland.enable";
-        description = ''
+        description = lib.mdDoc ''
           Whether to decompress fonts in
-          <filename>/run/current-system/sw/share/X11/fonts</filename>.
+          {file}`/run/current-system/sw/share/X11/fonts`.
         '';
       };
 
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix
index adc6654afc79..c0619fa31a32 100644
--- a/nixos/modules/config/fonts/fonts.nix
+++ b/nixos/modules/config/fonts/fonts.nix
@@ -57,13 +57,13 @@ in
         type = types.listOf types.path;
         default = [];
         example = literalExpression "[ pkgs.dejavu_fonts ]";
-        description = "List of primary font paths.";
+        description = lib.mdDoc "List of primary font paths.";
       };
 
       enableDefaultFonts = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable a basic set of fonts providing several font styles
           and families and reasonable coverage of Unicode.
         '';
diff --git a/nixos/modules/config/fonts/ghostscript.nix b/nixos/modules/config/fonts/ghostscript.nix
index b1dd81bf2d2e..c284c4a0b0ab 100644
--- a/nixos/modules/config/fonts/ghostscript.nix
+++ b/nixos/modules/config/fonts/ghostscript.nix
@@ -11,7 +11,7 @@ with lib;
       enableGhostscriptFonts = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add the fonts provided by Ghostscript (such as
           various URW fonts and the “Base-14” Postscript fonts) to the
           list of system fonts, making them available to X11
diff --git a/nixos/modules/config/gnu.nix b/nixos/modules/config/gnu.nix
index 255d9741ba71..d06b479e2af5 100644
--- a/nixos/modules/config/gnu.nix
+++ b/nixos/modules/config/gnu.nix
@@ -5,7 +5,7 @@
     gnu = lib.mkOption {
       type = lib.types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         When enabled, GNU software is chosen by default whenever a there is
         a choice between GNU and non-GNU software (e.g., GNU lsh
         vs. OpenSSH).
diff --git a/nixos/modules/config/gtk/gtk-icon-cache.nix b/nixos/modules/config/gtk/gtk-icon-cache.nix
index ff9aa7c6a047..62f0cc3f090f 100644
--- a/nixos/modules/config/gtk/gtk-icon-cache.nix
+++ b/nixos/modules/config/gtk/gtk-icon-cache.nix
@@ -7,7 +7,7 @@ with lib;
       type = types.bool;
       default = config.services.xserver.enable;
       defaultText = literalExpression "config.services.xserver.enable";
-      description = ''
+      description = lib.mdDoc ''
         Whether to build icon theme caches for GTK applications.
       '';
     };
@@ -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/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index 5b8d5b214496..b1efc00773dc 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -10,18 +10,18 @@ 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;
           }
         '';
         example = literalExpression "pkgs.glibcLocales";
-        description = ''
+        description = lib.mdDoc ''
           Customized pkg.glibcLocales package.
 
           Changing this option can disable handling of i18n.defaultLocale
@@ -33,7 +33,7 @@ with lib;
         type = types.str;
         default = "en_US.UTF-8";
         example = "nl_NL.UTF-8";
-        description = ''
+        description = lib.mdDoc ''
           The default locale.  It determines the language for program
           messages, the format for dates and times, sort order, and so on.
           It also determines the character set, such as UTF-8.
@@ -44,23 +44,38 @@ with lib;
         type = types.attrsOf types.str;
         default = {};
         example = { LC_MESSAGES = "en_US.UTF-8"; LC_TIME = "de_DE.UTF-8"; };
-        description = ''
+        description = lib.mdDoc ''
           A set of additional system-wide locale settings other than
-          <literal>LANG</literal> which can be configured with
-          <option>i18n.defaultLocale</option>.
+          `LANG` which can be configured with
+          {option}`i18n.defaultLocale`.
         '';
       };
 
       supportedLocales = mkOption {
         type = types.listOf types.str;
-        default = ["all"];
+        default = unique
+          (builtins.map (l: (replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8") (
+            [
+              "C.UTF-8"
+              "en_US.UTF-8"
+              config.i18n.defaultLocale
+            ] ++ (attrValues (filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings))
+          ));
+        defaultText = literalExpression ''
+          unique
+            (builtins.map (l: (replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8") (
+              [
+                "C.UTF-8"
+                config.i18n.defaultLocale
+              ] ++ (attrValues (filterAttrs (n: v: n != "LANGUAGE") config.i18n.extraLocaleSettings))
+            ))
+        '';
         example = ["en_US.UTF-8/UTF-8" "nl_NL.UTF-8/UTF-8" "nl_NL/ISO-8859-1"];
-        description = ''
+        description = lib.mdDoc ''
           List of locales that the system should support.  The value
-          <literal>"all"</literal> means that all locales supported by
+          `"all"` means that all locales supported by
           Glibc will be installed.  A full list of supported locales
-          can be found at <link
-          xlink:href="https://sourceware.org/git/?p=glibc.git;a=blob;f=localedata/SUPPORTED"/>.
+          can be found at <https://sourceware.org/git/?p=glibc.git;a=blob;f=localedata/SUPPORTED>.
         '';
       };
 
diff --git a/nixos/modules/config/iproute2.nix b/nixos/modules/config/iproute2.nix
index 5f41f3d21e45..8f49e7dbf7de 100644
--- a/nixos/modules/config/iproute2.nix
+++ b/nixos/modules/config/iproute2.nix
@@ -7,11 +7,11 @@ 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 = "";
-      description = ''
+      description = lib.mdDoc ''
         Verbatim lines to add to /etc/iproute2/rt_tables
       '';
     };
diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix
index 911c5b629a9a..df7a3f48236f 100644
--- a/nixos/modules/config/krb5/default.nix
+++ b/nixos/modules/config/krb5/default.nix
@@ -78,16 +78,16 @@ 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 = ''
+        description = lib.mdDoc ''
           The Kerberos implementation that will be present in
-          <literal>environment.systemPackages</literal> after enabling this
+          `environment.systemPackages` after enabling this
           service.
         '';
       };
@@ -101,7 +101,7 @@ in {
             default_realm = "ATHENA.MIT.EDU";
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Settings used by the Kerberos V5 library.
         '';
       };
@@ -121,7 +121,7 @@ in {
           };
         '';
         apply = attrs: filterEmbeddedMetadata attrs;
-        description = "Realm-specific contact information and settings.";
+        description = lib.mdDoc "Realm-specific contact information and settings.";
       };
 
       domain_realm = mkOption {
@@ -134,7 +134,7 @@ in {
           };
         '';
         apply = attrs: filterEmbeddedMetadata attrs;
-        description = ''
+        description = lib.mdDoc ''
           Map of server hostnames to Kerberos realms.
         '';
       };
@@ -153,7 +153,7 @@ in {
           };
         '';
         apply = attrs: filterEmbeddedMetadata attrs;
-        description = ''
+        description = lib.mdDoc ''
           Authentication paths for non-hierarchical cross-realm authentication.
         '';
       };
@@ -174,7 +174,7 @@ in {
           };
         '';
         apply = attrs: filterEmbeddedMetadata attrs;
-        description = ''
+        description = lib.mdDoc ''
           Settings used by some Kerberos V5 applications.
         '';
       };
@@ -190,7 +190,7 @@ in {
           };
         '';
         apply = attrs: filterEmbeddedMetadata attrs;
-        description = ''
+        description = lib.mdDoc ''
           Controls plugin module registration.
         '';
       };
@@ -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.
         '';
       };
 
@@ -235,14 +235,14 @@ in {
             admin_server = SYSLOG:NOTICE
             default      = SYSLOG:NOTICE
         '';
-        description = ''
-          Verbatim <literal>krb5.conf</literal> configuration.  Note that this
+        description = lib.mdDoc ''
+          Verbatim `krb5.conf` configuration.  Note that this
           is mutually exclusive with configuration via
-          <literal>libdefaults</literal>, <literal>realms</literal>,
-          <literal>domain_realm</literal>, <literal>capaths</literal>,
-          <literal>appdefaults</literal>, <literal>plugins</literal> and
-          <literal>extraConfig</literal> configuration options.  Consult
-          <literal>man krb5.conf</literal> for documentation.
+          `libdefaults`, `realms`,
+          `domain_realm`, `capaths`,
+          `appdefaults`, `plugins` and
+          `extraConfig` configuration options.  Consult
+          `man krb5.conf` for documentation.
         '';
       };
 
@@ -250,9 +250,9 @@ in {
         type = with types; nullOr str;
         default = null;
         example = "ATHENA.MIT.EDU";
-        description = ''
+        description = lib.mdDoc ''
           DEPRECATED, please use
-          <literal>krb5.libdefaults.default_realm</literal>.
+          `krb5.libdefaults.default_realm`.
         '';
       };
 
@@ -260,9 +260,9 @@ in {
         type = with types; nullOr str;
         default = null;
         example = "athena.mit.edu";
-        description = ''
+        description = lib.mdDoc ''
           DEPRECATED, please create a map of server hostnames to Kerberos realms
-          in <literal>krb5.domain_realm</literal>.
+          in `krb5.domain_realm`.
         '';
       };
 
@@ -270,9 +270,9 @@ in {
         type = with types; nullOr str;
         default = null;
         example = "kerberos.mit.edu";
-        description = ''
-          DEPRECATED, please pass a <literal>kdc</literal> attribute to a realm
-          in <literal>krb5.realms</literal>.
+        description = lib.mdDoc ''
+          DEPRECATED, please pass a `kdc` attribute to a realm
+          in `krb5.realms`.
         '';
       };
 
@@ -280,9 +280,9 @@ in {
         type = with types; nullOr str;
         default = null;
         example = "kerberos.mit.edu";
-        description = ''
-          DEPRECATED, please pass an <literal>admin_server</literal> attribute
-          to a realm in <literal>krb5.realms</literal>.
+        description = lib.mdDoc ''
+          DEPRECATED, please pass an `admin_server` attribute
+          to a realm in `krb5.realms`.
         '';
       };
     };
diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix
index 85cad8b93d8c..d2f01fb87d32 100644
--- a/nixos/modules/config/ldap.nix
+++ b/nixos/modules/config/ldap.nix
@@ -59,39 +59,39 @@ in
 
     users.ldap = {
 
-      enable = mkEnableOption "authentication against an LDAP server";
+      enable = mkEnableOption (lib.mdDoc "authentication against an LDAP server");
 
       loginPam = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to include authentication against LDAP in login PAM.";
+        description = lib.mdDoc "Whether to include authentication against LDAP in login PAM.";
       };
 
       nsswitch = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to include lookup against LDAP in NSS.";
+        description = lib.mdDoc "Whether to include lookup against LDAP in NSS.";
       };
 
       server = mkOption {
         type = types.str;
         example = "ldap://ldap.example.org/";
-        description = "The URL of the LDAP server.";
+        description = lib.mdDoc "The URL of the LDAP server.";
       };
 
       base = mkOption {
         type = types.str;
         example = "dc=example,dc=org";
-        description = "The distinguished name of the search base.";
+        description = lib.mdDoc "The distinguished name of the search base.";
       };
 
       useTLS = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, use TLS (encryption) over an LDAP (port 389)
           connection.  The alternative is to specify an LDAPS server (port
-          636) in <option>users.ldap.server</option> or to forego
+          636) in {option}`users.ldap.server` or to forego
           security.
         '';
       };
@@ -99,7 +99,7 @@ in
       timeLimit = mkOption {
         default = 0;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the time limit (in seconds) to use when performing
           searches. A value of zero (0), which is the default, is to
           wait indefinitely for searches to be completed.
@@ -110,7 +110,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to let the nslcd daemon (nss-pam-ldapd) handle the
             LDAP lookups for NSS and PAM. This can improve performance,
             and if you need to bind to the LDAP server with a password,
@@ -125,9 +125,9 @@ in
         extraConfig = mkOption {
           default =  "";
           type = types.lines;
-          description = ''
+          description = lib.mdDoc ''
             Extra configuration options that will be added verbatim at
-            the end of the nslcd configuration file (<literal>nslcd.conf(5)</literal>).
+            the end of the nslcd configuration file (`nslcd.conf(5)`).
           '' ;
         } ;
 
@@ -135,7 +135,7 @@ in
           default = "";
           example = "cn=admin,dc=example,dc=com";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The distinguished name to use to bind to the LDAP server
             when the root user tries to modify a user's password.
           '';
@@ -145,7 +145,7 @@ in
           default = "";
           example = "/run/keys/nslcd.rootpwmodpw";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The path to a file containing the credentials with which to bind to
             the LDAP server if the root user tries to change a user's password.
           '';
@@ -157,7 +157,7 @@ in
           default = "";
           example = "cn=admin,dc=example,dc=com";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The distinguished name to bind to the LDAP server with. If this
             is not specified, an anonymous bind will be done.
           '';
@@ -166,7 +166,7 @@ in
         passwordFile = mkOption {
           default = "/etc/ldap/bind.password";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The path to a file containing the credentials to use when binding
             to the LDAP server (if not binding anonymously).
           '';
@@ -175,10 +175,10 @@ in
         timeLimit = mkOption {
           default = 30;
           type = types.int;
-          description = ''
+          description = lib.mdDoc ''
             Specifies the time limit (in seconds) to use when connecting
             to the directory server. This is distinct from the time limit
-            specified in <option>users.ldap.timeLimit</option> and affects
+            specified in {option}`users.ldap.timeLimit` and affects
             the initial server connection only.
           '';
         };
@@ -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.
           '';
@@ -205,12 +205,12 @@ in
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options that will be added verbatim at
-          the end of the ldap configuration file (<literal>ldap.conf(5)</literal>).
-          If <option>users.ldap.daemon</option> is enabled, this
+          the end of the ldap configuration file (`ldap.conf(5)`).
+          If {option}`users.ldap.daemon` is enabled, this
           configuration will not be used. In that case, use
-          <option>users.ldap.daemon.extraConfig</option> instead.
+          {option}`users.ldap.daemon.extraConfig` instead.
         '' ;
       };
 
diff --git a/nixos/modules/config/locale.nix b/nixos/modules/config/locale.nix
index 6f0565881877..7716e121c712 100644
--- a/nixos/modules/config/locale.nix
+++ b/nixos/modules/config/locale.nix
@@ -22,9 +22,8 @@ in
         default = null;
         type = timezone;
         example = "America/New_York";
-        description = ''
-          The time zone used when displaying times and dates. See <link
-          xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"/>
+        description = lib.mdDoc ''
+          The time zone used when displaying times and dates. See <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
           for a comprehensive list of possible values for this setting.
 
           If null, the timezone will default to UTC and can be set imperatively
@@ -35,7 +34,7 @@ in
       hardwareClockInLocalTime = mkOption {
         default = false;
         type = types.bool;
-        description = "If set, keep the hardware clock in local time instead of UTC.";
+        description = lib.mdDoc "If set, keep the hardware clock in local time instead of UTC.";
       };
 
     };
@@ -44,18 +43,18 @@ in
 
       latitude = mkOption {
         type = types.float;
-        description = ''
+        description = lib.mdDoc ''
           Your current latitude, between
-          <literal>-90.0</literal> and <literal>90.0</literal>. Must be provided
+          `-90.0` and `90.0`. Must be provided
           along with longitude.
         '';
       };
 
       longitude = mkOption {
         type = types.float;
-        description = ''
+        description = lib.mdDoc ''
           Your current longitude, between
-          between <literal>-180.0</literal> and <literal>180.0</literal>. Must be
+          between `-180.0` and `180.0`. Must be
           provided along with latitude.
         '';
       };
@@ -63,9 +62,9 @@ in
       provider = mkOption {
         type = types.enum [ "manual" "geoclue2" ];
         default = "manual";
-        description = ''
+        description = lib.mdDoc ''
           The location provider to use for determining your location. If set to
-          <literal>manual</literal> you must also provide latitude/longitude.
+          `manual` you must also provide latitude/longitude.
         '';
       };
 
diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
index a3fed33afa18..4db0480b1553 100644
--- a/nixos/modules/config/malloc.nix
+++ b/nixos/modules/config/malloc.nix
@@ -77,24 +77,21 @@ 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>
+        :::
       '';
     };
   };
diff --git a/nixos/modules/config/mysql.nix b/nixos/modules/config/mysql.nix
new file mode 100644
index 000000000000..af20a5e95356
--- /dev/null
+++ b/nixos/modules/config/mysql.nix
@@ -0,0 +1,456 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.users.mysql;
+in
+{
+  options = {
+    users.mysql = {
+      enable = mkEnableOption (lib.mdDoc "Authentication against a MySQL/MariaDB database");
+      host = mkOption {
+        type = types.str;
+        example = "localhost";
+        description = lib.mdDoc "The hostname of the MySQL/MariaDB server";
+      };
+      database = mkOption {
+        type = types.str;
+        example = "auth";
+        description = lib.mdDoc "The name of the database containing the users";
+      };
+      user = mkOption {
+        type = types.str;
+        example = "nss-user";
+        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 = lib.mdDoc "The path to the file containing the password for the user";
+      };
+      pam = mkOption {
+        description = lib.mdDoc "Settings for `pam_mysql`";
+        type = types.submodule {
+          options = {
+            table = mkOption {
+              type = types.str;
+              example = "users";
+              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 = lib.mdDoc ''
+                The name of the table used for password alteration. If not defined, the value
+                of the `table` option will be used instead.
+              '';
+            };
+            userColumn = mkOption {
+              type = types.str;
+              example = "username";
+              description = lib.mdDoc "The name of the column that contains a unix login name.";
+            };
+            passwordColumn = mkOption {
+              type = types.str;
+              example = "password";
+              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 = 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:
+
+                - `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 {
+              example = "2";
+              type = types.enum [
+                "0" "plain"
+                "1" "Y"
+                "2" "mysql"
+                "3" "md5"
+                "4" "sha1"
+                "5" "drupal7"
+                "6" "joomla15"
+                "7" "ssha"
+                "8" "sha512"
+                "9" "sha256"
+              ];
+              description = lib.mdDoc ''
+                The method to encrypt the user's password:
+
+                - `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 = 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 = lib.mdDoc "Additional criteria for the query.";
+            };
+            verbose = mkOption {
+              type = types.bool;
+              default = false;
+              description = lib.mdDoc ''
+                If enabled, produces logs with detailed messages that describes what
+                `pam_mysql` is doing. May be useful for debugging.
+              '';
+            };
+            disconnectEveryOperation = mkOption {
+              type = types.bool;
+              default = false;
+              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.
+              '';
+            };
+            logging = {
+              enable = mkOption {
+                type = types.bool;
+                default = false;
+                description = lib.mdDoc "Enables logging of authentication attempts in the MySQL database.";
+              };
+              table = mkOption {
+                type = types.str;
+                example = "logs";
+                description = lib.mdDoc "The name of the table to which logs are written.";
+              };
+              msgColumn = mkOption {
+                type = types.str;
+                example = "msg";
+                description = lib.mdDoc ''
+                  The name of the column in the log table to which the description
+                  of the performed operation is stored.
+                '';
+              };
+              userColumn = mkOption {
+                type = types.str;
+                example = "user";
+                description = lib.mdDoc ''
+                  The name of the column in the log table to which the name of the
+                  user being authenticated is stored.
+                '';
+              };
+              pidColumn = mkOption {
+                type = types.str;
+                example = "pid";
+                description = lib.mdDoc ''
+                  The name of the column in the log table to which the pid of the
+                  process utilising the `pam_mysql's` authentication
+                  service is stored.
+                '';
+              };
+              hostColumn = mkOption {
+                type = types.str;
+                example = "host";
+                description = lib.mdDoc ''
+                  The name of the column in the log table to which the name of the user
+                  being authenticated is stored.
+                '';
+              };
+              rHostColumn = mkOption {
+                type = types.str;
+                example = "rhost";
+                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 `pam_set_item(PAM_RHOST)`.
+                '';
+              };
+              timeColumn = mkOption {
+                type = types.str;
+                example = "timestamp";
+                description = lib.mdDoc ''
+                  The name of the column in the log table to which the timestamp of the
+                  log entry is stored.
+                '';
+              };
+            };
+          };
+        };
+      };
+      nss = mkOption {
+        description = lib.mdDoc ''
+          Settings for `libnss-mysql`.
+
+          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 = {
+            getpwnam = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
+                FROM users \
+                WHERE username='%1$s' \
+                LIMIT 1
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getpwnam](https://man7.org/linux/man-pages/man3/getpwnam.3.html)
+                syscall.
+              '';
+            };
+            getpwuid = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' \
+                FROM users \
+                WHERE uid='%1$u' \
+                LIMIT 1
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getpwuid](https://man7.org/linux/man-pages/man3/getpwuid.3.html)
+                syscall.
+              '';
+            };
+            getspnam = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT username,password,'1','0','99999','0','0','-1','0' \
+                FROM users \
+                WHERE username='%1$s' \
+                LIMIT 1
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getspnam](https://man7.org/linux/man-pages/man3/getspnam.3.html)
+                syscall.
+              '';
+            };
+            getpwent = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT username,'x',uid,'5000','MySQL User', CONCAT('/home/',username),'/run/sw/current-system/bin/bash' FROM users
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getpwent](https://man7.org/linux/man-pages/man3/getpwent.3.html)
+                syscall.
+              '';
+            };
+            getspent = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT username,password,'1','0','99999','0','0','-1','0' FROM users
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getspent](https://man7.org/linux/man-pages/man3/getspent.3.html)
+                syscall.
+              '';
+            };
+            getgrnam = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT name,password,gid FROM groups WHERE name='%1$s' LIMIT 1
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getgrnam](https://man7.org/linux/man-pages/man3/getgrnam.3.html)
+                syscall.
+              '';
+            };
+            getgrgid = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT name,password,gid FROM groups WHERE gid='%1$u' LIMIT 1
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getgrgid](https://man7.org/linux/man-pages/man3/getgrgid.3.html)
+                syscall.
+              '';
+            };
+            getgrent = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT name,password,gid FROM groups
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [getgrent](https://man7.org/linux/man-pages/man3/getgrent.3.html)
+                syscall.
+              '';
+            };
+            memsbygid = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT username FROM grouplist WHERE gid='%1$u'
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [memsbygid](https://man7.org/linux/man-pages/man3/memsbygid.3.html)
+                syscall.
+              '';
+            };
+            gidsbymem = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = literalExpression ''
+                SELECT gid FROM grouplist WHERE username='%1$s'
+              '';
+              description = lib.mdDoc ''
+                SQL query for the [gidsbymem](https://man7.org/linux/man-pages/man3/gidsbymem.3.html)
+                syscall.
+              '';
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    system.nssModules = [ pkgs.libnss-mysql ];
+    system.nssDatabases.shadow = [ "mysql" ];
+    system.nssDatabases.group = [ "mysql" ];
+    system.nssDatabases.passwd = [ "mysql" ];
+
+    environment.etc."security/pam_mysql.conf" = {
+      user = "root";
+      group = "root";
+      mode = "0600";
+      # password will be added from password file in activation script
+      text = ''
+        users.host=${cfg.host}
+        users.db_user=${cfg.user}
+        users.database=${cfg.database}
+        users.table=${cfg.pam.table}
+        users.user_column=${cfg.pam.userColumn}
+        users.password_column=${cfg.pam.passwordColumn}
+        users.password_crypt=${cfg.pam.passwordCrypt}
+        users.disconnect_every_operation=${if cfg.pam.disconnectEveryOperation then "1" else "0"}
+        verbose=${if cfg.pam.verbose then "1" else "0"}
+      '' + optionalString (cfg.pam.cryptDefault != null) ''
+        users.use_${cfg.pam.cryptDefault}=1
+      '' + optionalString (cfg.pam.where != null) ''
+        users.where_clause=${cfg.pam.where}
+      '' + optionalString (cfg.pam.statusColumn != null) ''
+        users.status_column=${cfg.pam.statusColumn}
+      '' + optionalString (cfg.pam.updateTable != null) ''
+        users.update_table=${cfg.pam.updateTable}
+      '' + optionalString cfg.pam.logging.enable ''
+        log.enabled=true
+        log.table=${cfg.pam.logging.table}
+        log.message_column=${cfg.pam.logging.msgColumn}
+        log.pid_column=${cfg.pam.logging.pidColumn}
+        log.user_column=${cfg.pam.logging.userColumn}
+        log.host_column=${cfg.pam.logging.hostColumn}
+        log.rhost_column=${cfg.pam.logging.rHostColumn}
+        log.time_column=${cfg.pam.logging.timeColumn}
+      '';
+    };
+
+    environment.etc."libnss-mysql.cfg" = {
+      mode = "0600";
+      user = config.services.nscd.user;
+      group = config.services.nscd.group;
+      text = optionalString (cfg.nss.getpwnam != null) ''
+        getpwnam ${cfg.nss.getpwnam}
+      '' + optionalString (cfg.nss.getpwuid != null) ''
+        getpwuid ${cfg.nss.getpwuid}
+      '' + optionalString (cfg.nss.getspnam != null) ''
+        getspnam ${cfg.nss.getspnam}
+      '' + optionalString (cfg.nss.getpwent != null) ''
+        getpwent ${cfg.nss.getpwent}
+      '' + optionalString (cfg.nss.getspent != null) ''
+        getspent ${cfg.nss.getspent}
+      '' + optionalString (cfg.nss.getgrnam != null) ''
+        getgrnam ${cfg.nss.getgrnam}
+      '' + optionalString (cfg.nss.getgrgid != null) ''
+        getgrgid ${cfg.nss.getgrgid}
+      '' + optionalString (cfg.nss.getgrent != null) ''
+        getgrent ${cfg.nss.getgrent}
+      '' + optionalString (cfg.nss.memsbygid != null) ''
+        memsbygid ${cfg.nss.memsbygid}
+      '' + optionalString (cfg.nss.gidsbymem != null) ''
+        gidsbymem ${cfg.nss.gidsbymem}
+      '' + ''
+        host ${cfg.host}
+        database ${cfg.database}
+      '';
+    };
+
+    environment.etc."libnss-mysql-root.cfg" = {
+      mode = "0600";
+      user = config.services.nscd.user;
+      group = config.services.nscd.group;
+      # password will be added from password file in activation script
+      text = ''
+        username ${cfg.user}
+      '';
+    };
+
+    # Activation script to append the password from the password file
+    # to the configuration files. It also fixes the owner of the
+    # libnss-mysql-root.cfg because it is changed to root after the
+    # password is appended.
+    system.activationScripts.mysql-auth-passwords = ''
+      if [[ -r ${cfg.passwordFile} ]]; then
+        org_umask=$(umask)
+        umask 0077
+
+        conf_nss="$(mktemp)"
+        cp /etc/libnss-mysql-root.cfg $conf_nss
+        printf 'password %s\n' "$(cat ${cfg.passwordFile})" >> $conf_nss
+        mv -fT "$conf_nss" /etc/libnss-mysql-root.cfg
+        chown ${config.services.nscd.user}:${config.services.nscd.group} /etc/libnss-mysql-root.cfg
+
+        conf_pam="$(mktemp)"
+        cp /etc/security/pam_mysql.conf $conf_pam
+        printf 'users.db_passwd=%s\n' "$(cat ${cfg.passwordFile})" >> $conf_pam
+        mv -fT "$conf_pam" /etc/security/pam_mysql.conf
+
+        umask $org_umask
+      fi
+    '';
+  };
+}
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index bebfeb352c01..aa6d75e199f9 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -28,17 +28,17 @@ in
           "192.168.0.2" = [ "fileserver.local" "nameserver.local" ];
         };
       '';
-      description = ''
+      description = lib.mdDoc ''
         Locally defined maps of hostnames to IP addresses.
       '';
     };
 
     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 = ''
-        Files that should be concatenated together to form <filename>/etc/hosts</filename>.
+      description = lib.mdDoc ''
+        Files that should be concatenated together to form {file}`/etc/hosts`.
       '';
     };
 
@@ -46,9 +46,9 @@ in
       type = types.lines;
       default = "";
       example = "192.168.0.1 lanlocalhost";
-      description = ''
-        Additional verbatim entries to be appended to <filename>/etc/hosts</filename>.
-        For adding hosts from derivation results, use <option>networking.hostFiles</option> instead.
+      description = lib.mdDoc ''
+        Additional verbatim entries to be appended to {file}`/etc/hosts`.
+        For adding hosts from derivation results, use {option}`networking.hostFiles` instead.
       '';
     };
 
@@ -60,7 +60,7 @@ in
         "3.nixos.pool.ntp.org"
       ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         The set of NTP servers from which to synchronise.
       '';
     };
@@ -70,7 +70,7 @@ in
       default = lib.mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the default value for httpProxy, httpsProxy, ftpProxy and rsyncProxy.
         '';
         example = "http://127.0.0.1:3128";
@@ -80,7 +80,7 @@ in
         type = types.nullOr types.str;
         default = cfg.proxy.default;
         defaultText = literalExpression "config.${opt.proxy.default}";
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the http_proxy environment variable.
         '';
         example = "http://127.0.0.1:3128";
@@ -90,7 +90,7 @@ in
         type = types.nullOr types.str;
         default = cfg.proxy.default;
         defaultText = literalExpression "config.${opt.proxy.default}";
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the https_proxy environment variable.
         '';
         example = "http://127.0.0.1:3128";
@@ -100,7 +100,7 @@ in
         type = types.nullOr types.str;
         default = cfg.proxy.default;
         defaultText = literalExpression "config.${opt.proxy.default}";
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the ftp_proxy environment variable.
         '';
         example = "http://127.0.0.1:3128";
@@ -110,7 +110,7 @@ in
         type = types.nullOr types.str;
         default = cfg.proxy.default;
         defaultText = literalExpression "config.${opt.proxy.default}";
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the rsync_proxy environment variable.
         '';
         example = "http://127.0.0.1:3128";
@@ -120,7 +120,7 @@ in
         type = types.nullOr types.str;
         default = cfg.proxy.default;
         defaultText = literalExpression "config.${opt.proxy.default}";
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the all_proxy environment variable.
         '';
         example = "http://127.0.0.1:3128";
@@ -129,7 +129,7 @@ in
       noProxy = lib.mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the no_proxy environment variable.
           If a default proxy is used and noProxy is null,
           then noProxy will be set to 127.0.0.1,localhost.
@@ -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/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 14fe180d0bc5..5a2a1a0e8ac8 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -10,7 +10,7 @@ with lib;
     environment.noXlibs = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Switch off the options in the default configuration that
         require X11 libraries. This includes client-side font
         configuration and SSH forwarding of X11 authentication
@@ -27,9 +27,17 @@ 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-headless;
+      ffmpeg_5 = super.ffmpeg_5-headless;
+      gobject-introspection = super.gobject-introspection.override { x11Support = false; };
+      imagemagick = super.imagemagick.override { libX11Support = false; libXtSupport = false; };
+      imagemagickBig = super.imagemagickBig.override { libX11Support = false; libXtSupport = false; };
+      libextractor = super.libextractor.override { gstreamerSupport = false; gtkSupport = false; };
+      libva = super.libva-minimal;
+      msmtp = super.msmtp.override { withKeyring = 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 +45,10 @@ 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; };
+      pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
+      qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
+      zbar = super.zbar.override { enableVideo = false; withXorg = false; };
     }));
   };
 }
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index e494ff5f74d5..b004072813bd 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -13,10 +13,10 @@ with lib;
       type = types.listOf types.path;
       internal = true;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Search path for NSS (Name Service Switch) modules.  This allows
         several DNS resolution methods to be specified via
-        <filename>/etc/nsswitch.conf</filename>.
+        {file}`/etc/nsswitch.conf`.
       '';
       apply = list:
         {
@@ -28,8 +28,8 @@ with lib;
     system.nssDatabases = {
       passwd = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of passwd entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of passwd entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended while "systemd" is appended if nscd is enabled.
 
@@ -40,8 +40,8 @@ with lib;
 
       group = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of group entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of group entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended while "systemd" is appended if nscd is enabled.
 
@@ -52,8 +52,8 @@ with lib;
 
       shadow = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of shadow entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of shadow entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended.
 
@@ -64,8 +64,8 @@ with lib;
 
       hosts = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of hosts entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of hosts entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended, and "dns" and "myhostname" are always appended.
 
@@ -76,8 +76,8 @@ with lib;
 
       services = mkOption {
         type = types.listOf types.str;
-        description = ''
-          List of services entries to configure in <filename>/etc/nsswitch.conf</filename>.
+        description = lib.mdDoc ''
+          List of services entries to configure in {file}`/etc/nsswitch.conf`.
 
           Note that "files" is always prepended.
 
diff --git a/nixos/modules/config/power-management.nix b/nixos/modules/config/power-management.nix
index 710842e1503b..e7fd02920e0d 100644
--- a/nixos/modules/config/power-management.nix
+++ b/nixos/modules/config/power-management.nix
@@ -20,7 +20,7 @@ in
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             Whether to enable power management.  This includes support
             for suspend-to-RAM and powersave features on laptops.
           '';
@@ -29,7 +29,7 @@ in
       resumeCommands = mkOption {
         type = types.lines;
         default = "";
-        description = "Commands executed after the system resumes from suspend-to-RAM.";
+        description = lib.mdDoc "Commands executed after the system resumes from suspend-to-RAM.";
       };
 
       powerUpCommands = mkOption {
@@ -39,7 +39,7 @@ in
           "''${pkgs.hdparm}/sbin/hdparm -B 255 /dev/sda"
         '';
         description =
-          ''
+          lib.mdDoc ''
             Commands executed when the machine powers up.  That is,
             they're executed both when the system first boots and when
             it resumes from suspend or hibernation.
@@ -53,7 +53,7 @@ in
           "''${pkgs.hdparm}/sbin/hdparm -B 255 /dev/sda"
         '';
         description =
-          ''
+          lib.mdDoc ''
             Commands executed when the machine powers down.  That is,
             they're executed both when the system shuts down and when
             it goes to suspend or hibernation.
@@ -91,10 +91,10 @@ in
 
     systemd.services.post-resume =
       { description = "Post-Resume Actions";
-        after = [ "suspend.target" "hibernate.target" "hybrid-sleep.target" ];
+        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/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index 01555d28b73f..80ff6c1aabf7 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -89,7 +89,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the PulseAudio sound server.
         '';
       };
@@ -97,12 +97,12 @@ in {
       systemWide = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If false, a PulseAudio server is launched automatically for
           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.
@@ -112,7 +112,7 @@ in {
       support32Bit = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to include the 32-bit pulseaudio libraries in the system or not.
           This is only useful on 64-bit systems and currently limited to x86_64-linux.
         '';
@@ -120,7 +120,7 @@ in {
 
       configFile = mkOption {
         type = types.nullOr types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path to the default configuration options the PulseAudio server
           should use. By default, the "default.pa" configuration
           from the PulseAudio distribution is used.
@@ -130,8 +130,8 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Literal string to append to <literal>configFile</literal>
+        description = lib.mdDoc ''
+          Literal string to append to `configFile`
           and the config file generated by the pulseaudio module.
         '';
       };
@@ -139,7 +139,7 @@ in {
       extraClientConf = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration appended to pulse/client.conf file.
         '';
       };
@@ -151,10 +151,10 @@ in {
                   else pkgs.pulseaudio;
         defaultText = literalExpression "pkgs.pulseaudio";
         example = literalExpression "pkgs.pulseaudioFull";
-        description = ''
+        description = lib.mdDoc ''
           The PulseAudio derivation to use.  This can be used to enable
           features (such as JACK support, Bluetooth) via the
-          <literal>pulseaudioFull</literal> package.
+          `pulseaudioFull` package.
         '';
       };
 
@@ -162,7 +162,7 @@ in {
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.pulseaudio-modules-bt ]";
-        description = ''
+        description = lib.mdDoc ''
           Extra pulseaudio modules to use. This is intended for out-of-tree
           pulseaudio modules like extra bluetooth codecs.
 
@@ -174,7 +174,7 @@ in {
         logLevel = mkOption {
           type = types.str;
           default = "notice";
-          description = ''
+          description = lib.mdDoc ''
             The log level that the system-wide pulseaudio daemon should use,
             if activated.
           '';
@@ -183,29 +183,29 @@ in {
         config = mkOption {
           type = types.attrsOf types.unspecified;
           default = {};
-          description = "Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.";
+          description = lib.mdDoc "Config of the pulse daemon. See `man pulse-daemon.conf`.";
           example = literalExpression ''{ realtime-scheduling = "yes"; }'';
         };
       };
 
       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 = [];
             example = literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
-            description = ''
+            description = lib.mdDoc ''
               A list of IP subnets that are allowed to stream to the server.
             '';
           };
@@ -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/nixos/modules/config/qt5.nix b/nixos/modules/config/qt5.nix
index eabba9ad95f0..cb3180d7b96a 100644
--- a/nixos/modules/config/qt5.nix
+++ b/nixos/modules/config/qt5.nix
@@ -8,47 +8,54 @@ let
 
   isQGnome = cfg.platformTheme == "gnome" && builtins.elem cfg.style ["adwaita" "adwaita-dark"];
   isQtStyle = cfg.platformTheme == "gtk2" && !(builtins.elem cfg.style ["adwaita" "adwaita-dark"]);
+  isQt5ct = cfg.platformTheme == "qt5ct";
+  isLxqt = cfg.platformTheme == "lxqt";
+  isKde = cfg.platformTheme == "kde";
 
   packages = if isQGnome then [ pkgs.qgnomeplatform pkgs.adwaita-qt ]
     else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins ]
+    else if isQt5ct then [ pkgs.libsForQt5.qt5ct ]
+    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.";
 
 in
 
 {
+  meta.maintainers = [ maintainers.romildo ];
 
   options = {
     qt5 = {
 
-      enable = mkEnableOption "Qt5 theming configuration";
+      enable = mkEnableOption (lib.mdDoc "Qt5 theming configuration");
 
       platformTheme = mkOption {
         type = types.enum [
           "gtk2"
           "gnome"
+          "lxqt"
+          "qt5ct"
+          "kde"
         ];
         example = "gnome";
         relatedPackages = [
           "qgnomeplatform"
           ["libsForQt5" "qtstyleplugins"]
+          ["libsForQt5" "qt5ct"]
+          ["lxqt" "lxqt-qtplugin"]
+          ["libsForQt5" "plasma-integration"]
         ];
-        description = ''
-          Selects the platform theme to use for Qt5 applications.</para>
-          <para>The options are
-          <variablelist>
-            <varlistentry>
-              <term><literal>gtk</literal></term>
-              <listitem><para>Use GTK theme with
-                <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><literal>gnome</literal></term>
-              <listitem><para>Use GNOME theme with
-                <link xlink:href="https://github.com/FedoraQt/QGnomePlatform">qgnomeplatform</link>
-              </para></listitem>
-            </varlistentry>
-          </variablelist>
+        description = lib.mdDoc ''
+          Selects the platform theme to use for Qt5 applications.
+
+          The options are
+          - `gtk`: Use GTK theme with [qtstyleplugins](https://github.com/qt/qtstyleplugins)
+          - `gnome`: Use GNOME theme with [qgnomeplatform](https://github.com/FedoraQt/QGnomePlatform)
+          - `lxqt`: Use LXQt style set using the [lxqt-config-appearance](https://github.com/lxqt/lxqt-config)
+             application.
+          - `qt5ct`: Use Qt style set using the [qt5ct](https://sourceforge.net/projects/qt5ct/)
+             application.
+          - `kde`: Use Qt settings from Plasma.
         '';
       };
 
@@ -66,27 +73,14 @@ in
           "adwaita-qt"
           ["libsForQt5" "qtstyleplugins"]
         ];
-        description = ''
-          Selects the style to use for Qt5 applications.</para>
-          <para>The options are
-          <variablelist>
-            <varlistentry>
-              <term><literal>adwaita</literal></term>
-              <term><literal>adwaita-dark</literal></term>
-              <listitem><para>Use Adwaita Qt style with
-                <link xlink:href="https://github.com/FedoraQt/adwaita-qt">adwaita</link>
-              </para></listitem>
-            </varlistentry>
-            <varlistentry>
-              <term><literal>cleanlooks</literal></term>
-              <term><literal>gtk2</literal></term>
-              <term><literal>motif</literal></term>
-              <term><literal>plastique</literal></term>
-              <listitem><para>Use styles from
-                <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
-              </para></listitem>
-            </varlistentry>
-          </variablelist>
+        description = lib.mdDoc ''
+          Selects the style to use for Qt5 applications.
+
+          The options are
+          - `adwaita`, `adwaita-dark`: Use Adwaita Qt style with
+            [adwaita](https://github.com/FedoraQt/adwaita-qt)
+          - `cleanlooks`, `gtk2`, `motif`, `plastique`: Use styles from
+            [qtstyleplugins](https://github.com/qt/qtstyleplugins)
         '';
       };
     };
@@ -96,7 +90,7 @@ in
 
     environment.variables.QT_QPA_PLATFORMTHEME = cfg.platformTheme;
 
-    environment.variables.QT_STYLE_OVERRIDE = cfg.style;
+    environment.variables.QT_STYLE_OVERRIDE = mkIf (! (isQt5ct || isLxqt || isKde)) cfg.style;
 
     environment.systemPackages = packages;
 
diff --git a/nixos/modules/config/resolvconf.nix b/nixos/modules/config/resolvconf.nix
index 4499481811fd..76605a063a47 100644
--- a/nixos/modules/config/resolvconf.nix
+++ b/nixos/modules/config/resolvconf.nix
@@ -49,15 +49,28 @@ in
         type = types.bool;
         default = !(config.environment.etc ? "resolv.conf");
         defaultText = literalExpression ''!(config.environment.etc ? "resolv.conf")'';
-        description = ''
-          DNS configuration is managed by resolvconf.
+        description = lib.mdDoc ''
+          Whether DNS configuration is managed by resolvconf.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.openresolv;
+        defaultText = literalExpression "pkgs.openresolv";
+        description = lib.mdDoc ''
+          The package that provides the system-wide resolvconf command. Defaults to `openresolv`
+          if this module is enabled. Otherwise, can be used by other modules (for example {option}`services.resolved`) to
+          provide a compatibility layer.
+
+          This option generally shouldn't be set by the user.
         '';
       };
 
       dnsSingleRequest = lib.mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Recent versions of glibc will issue both ipv4 (A) and ipv6 (AAAA)
           address queries at the same time, from the same port. Sometimes upstream
           routers will systemically drop the ipv4 queries. The symptom of this problem is
@@ -70,9 +83,9 @@ in
       dnsExtensionMechanism = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          Enable the <code>edns0</code> option in <filename>resolv.conf</filename>. With
-          that option set, <code>glibc</code> supports use of the extension mechanisms for
+        description = lib.mdDoc ''
+          Enable the `edns0` option in {file}`resolv.conf`. With
+          that option set, `glibc` supports use of the extension mechanisms for
           DNS (EDNS) specified in RFC 2671. The most popular user of that feature is DNSSEC,
           which does not work without it.
         '';
@@ -82,8 +95,8 @@ in
         type = types.lines;
         default = "";
         example = "libc=NO";
-        description = ''
-          Extra configuration to append to <filename>resolvconf.conf</filename>.
+        description = lib.mdDoc ''
+          Extra configuration to append to {file}`resolvconf.conf`.
         '';
       };
 
@@ -91,15 +104,15 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "ndots:1" "rotate" ];
-        description = ''
-          Set the options in <filename>/etc/resolv.conf</filename>.
+        description = lib.mdDoc ''
+          Set the options in {file}`/etc/resolv.conf`.
         '';
       };
 
       useLocalResolver = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Use local DNS server for resolving.
         '';
       };
@@ -119,10 +132,12 @@ in
             exit 1
           ''
         else configText;
+
+      environment.systemPackages = [ cfg.package ];
     }
 
     (mkIf cfg.enable {
-      environment.systemPackages = [ pkgs.openresolv ];
+      networking.resolvconf.package = pkgs.openresolv;
 
       systemd.services.resolvconf = {
         description = "resolvconf update";
@@ -134,7 +149,7 @@ in
 
         serviceConfig = {
           Type = "oneshot";
-          ExecStart = "${pkgs.openresolv}/bin/resolvconf -u";
+          ExecStart = "${cfg.package}/bin/resolvconf -u";
           RemainAfterExit = true;
         };
       };
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index ae3f618e273c..50bb9b17783b 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -35,7 +35,7 @@ in
     environment.variables = mkOption {
       default = {};
       example = { EDITOR = "nvim"; VISUAL = "nvim"; };
-      description = ''
+      description = lib.mdDoc ''
         A set of environment variables used in the global environment.
         These variables will be set on shell initialisation (e.g. in /etc/profile).
         The value of each variable can be either a string or a list of
@@ -48,7 +48,7 @@ in
 
     environment.profiles = mkOption {
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         A list of profiles used to setup the global environment.
       '';
       type = types.listOf types.str;
@@ -57,10 +57,10 @@ in
     environment.profileRelativeEnvVars = mkOption {
       type = types.attrsOf (types.listOf types.str);
       example = { PATH = [ "/bin" ]; MANPATH = [ "/man" "/share/man" ]; };
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of environment variable.  Each attribute maps to a list
         of relative paths.  Each relative path is appended to the each profile
-        of <option>environment.profiles</option> to form the content of the
+        of {option}`environment.profiles` to form the content of the
         corresponding environment variable.
       '';
     };
@@ -68,7 +68,7 @@ in
     # !!! isn't there a better way?
     environment.extraInit = mkOption {
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Shell script code called during global environment initialisation
         after all variables and profileVariables have been set.
         This code is assumed to be shell-independent, which means you should
@@ -79,7 +79,7 @@ in
 
     environment.shellInit = mkOption {
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Shell script code called during shell initialisation.
         This code is assumed to be shell-independent, which means you should
         stick to pure sh without sh word split.
@@ -89,7 +89,7 @@ in
 
     environment.loginShellInit = mkOption {
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Shell script code called during login shell initialisation.
         This code is assumed to be shell-independent, which means you should
         stick to pure sh without sh word split.
@@ -99,7 +99,7 @@ in
 
     environment.interactiveShellInit = mkOption {
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Shell script code called during interactive shell initialisation.
         This code is assumed to be shell-independent, which means you should
         stick to pure sh without sh word split.
@@ -109,17 +109,17 @@ in
 
     environment.shellAliases = mkOption {
       example = { l = null; ll = "ls -l"; };
-      description = ''
+      description = lib.mdDoc ''
         An attribute set that maps aliases (the top level attribute names in
         this option) to command strings or directly to build outputs. The
         aliases are added to all users' shells.
-        Aliases mapped to <code>null</code> are ignored.
+        Aliases mapped to `null` are ignored.
       '';
       type = with types; attrsOf (nullOr (either str path));
     };
 
     environment.homeBinInPath = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Include ~/bin/ in $PATH.
       '';
       default = false;
@@ -127,7 +127,7 @@ in
     };
 
     environment.localBinInPath = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Add ~/.local/bin/ to $PATH
       '';
       default = false;
@@ -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.
       '';
@@ -151,9 +151,9 @@ in
     environment.shells = mkOption {
       default = [];
       example = literalExpression "[ pkgs.bashInteractive pkgs.zsh ]";
-      description = ''
+      description = lib.mdDoc ''
         A list of permissible login shells for user accounts.
-        No need to mention <literal>/bin/sh</literal>
+        No need to mention `/bin/sh`
         here, it is placed into this list implicitly.
       '';
       type = types.listOf (types.either types.shellPackage types.path);
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index 2b94b954cb80..76a054b100eb 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -14,7 +14,7 @@ let
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Encrypt swap device with a random key. This way you won't have a persistent swap device.
 
           WARNING: Don't try to hibernate when you have at least one swap partition with
@@ -31,7 +31,7 @@ let
         default = "aes-xts-plain64";
         example = "serpent-xts-plain64";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Use specified cipher for randomEncryption.
 
           Hint: Run "cryptsetup benchmark" to see which one is fastest on your machine.
@@ -42,7 +42,7 @@ let
         default = "/dev/urandom";
         example = "/dev/random";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Define the source of randomness to obtain a random key for encryption.
         '';
       };
@@ -50,7 +50,7 @@ let
       allowDiscards = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow TRIM requests to the underlying device. This option
           has security implications; please read the LUKS documentation before
           activating it.
@@ -67,14 +67,14 @@ let
       device = mkOption {
         example = "/dev/sda3";
         type = types.str;
-        description = "Path of the device or swap file.";
+        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`.
         '';
       };
 
@@ -82,7 +82,7 @@ let
         default = null;
         example = 2048;
         type = types.nullOr types.int;
-        description = ''
+        description = lib.mdDoc ''
           If this option is set, ‘device’ is interpreted as the
           path of a swapfile that will be created automatically
           with the indicated size (in megabytes).
@@ -93,7 +93,7 @@ let
         default = null;
         example = 2048;
         type = types.nullOr types.int;
-        description = ''
+        description = lib.mdDoc ''
           Specify the priority of the swap device. Priority is a value between 0 and 32767.
           Higher numbers indicate higher priority.
           null lets the kernel choose a priority, which will show up as a negative value.
@@ -108,7 +108,7 @@ let
           source = "/dev/random";
         };
         type = types.coercedTo types.bool randomEncryptionCoerce (types.submodule randomEncryptionOpts);
-        description = ''
+        description = lib.mdDoc ''
           Encrypt swap device with a random key. This way you won't have a persistent swap device.
 
           HINT: run "cryptsetup benchmark" to test cipher performance on your machine.
@@ -127,7 +127,7 @@ let
         default = null;
         example = "once";
         type = types.nullOr (types.enum ["once" "pages" "both" ]);
-        description = ''
+        description = lib.mdDoc ''
           Specify the discard policy for the swap device. If "once", then the
           whole swap space is discarded at swapon invocation. If "pages",
           asynchronous discard on freed pages is performed, before returning to
@@ -140,7 +140,7 @@ let
         default = [ "defaults" ];
         example = [ "nofail" ];
         type = types.listOf types.nonEmptyStr;
-        description = ''
+        description = lib.mdDoc ''
           Options used to mount the swap.
         '';
       };
@@ -160,7 +160,7 @@ let
     config = rec {
       device = mkIf options.label.isDefined
         "/dev/disk/by-label/${config.label}";
-      deviceName = lib.replaceChars ["\\"] [""] (escapeSystemdPath config.device);
+      deviceName = lib.replaceStrings ["\\"] [""] (escapeSystemdPath config.device);
       realDevice = if config.randomEncryption.enable then "/dev/mapper/${deviceName}" else config.device;
     };
 
@@ -181,13 +181,13 @@ in
         { device = "/var/swapfile"; }
         { label = "bigswap"; }
       ];
-      description = ''
+      description = lib.mdDoc ''
         The swap devices and swap files.  These must have been
-        initialised using <command>mkswap</command>.  Each element
+        initialised using {command}`mkswap`.  Each element
         should be an attribute set specifying either the path of the
-        swap device or file (<literal>device</literal>) or the label
-        of the swap device (<literal>label</literal>, see
-        <command>mkswap -L</command>).  Using a label is
+        swap device or file (`device`) or the label
+        of the swap device (`label`, see
+        {command}`mkswap -L`).  Using a label is
         recommended.
       '';
 
diff --git a/nixos/modules/config/sysctl.nix b/nixos/modules/config/sysctl.nix
index db1f5284f504..4346c88f7688 100644
--- a/nixos/modules/config/sysctl.nix
+++ b/nixos/modules/config/sysctl.nix
@@ -21,21 +21,34 @@ 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 = ''
+      description = lib.mdDoc ''
         Runtime parameters of the Linux kernel, as set by
-        <citerefentry><refentrytitle>sysctl</refentrytitle>
-        <manvolnum>8</manvolnum></citerefentry>.  Note that sysctl
+        {manpage}`sysctl(8)`.  Note that sysctl
         parameters names must be enclosed in quotes
-        (e.g. <literal>"vm.swappiness"</literal> instead of
-        <literal>vm.swappiness</literal>).  The value of each
+        (e.g. `"vm.swappiness"` instead of
+        `vm.swappiness`).  The value of each
         parameter may be a string, integer, boolean, or null
         (signifying the option will not appear at all).
       '';
+
     };
 
   };
diff --git a/nixos/modules/config/system-environment.nix b/nixos/modules/config/system-environment.nix
index d2a66b8d932d..5b226d5079b0 100644
--- a/nixos/modules/config/system-environment.nix
+++ b/nixos/modules/config/system-environment.nix
@@ -16,7 +16,7 @@ in
 
     environment.sessionVariables = mkOption {
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         A set of environment variables used in the global environment.
         These variables will be set by PAM early in the login process.
 
@@ -25,12 +25,12 @@ in
         colon characters.
 
         Note, due to limitations in the PAM format values may not
-        contain the <literal>"</literal> character.
+        contain the `"` character.
 
         Also, these variables are merged into
-        <xref linkend="opt-environment.variables"/> and it is
+        [](#opt-environment.variables) and it is
         therefore not possible to use PAM style variables such as
-        <code>@{HOME}</code>.
+        `@{HOME}`.
       '';
       type = with types; attrsOf (either str (listOf str));
       apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
@@ -39,26 +39,23 @@ in
     environment.profileRelativeSessionVariables = mkOption {
       type = types.attrsOf (types.listOf types.str);
       example = { PATH = [ "/bin" ]; MANPATH = [ "/man" "/share/man" ]; };
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of environment variable used in the global
         environment. These variables will be set by PAM early in the
         login process.
 
         Variable substitution is available as described in
-        <citerefentry>
-          <refentrytitle>pam_env.conf</refentrytitle>
-          <manvolnum>5</manvolnum>
-        </citerefentry>.
+        {manpage}`pam_env.conf(5)`.
 
         Each attribute maps to a list of relative paths. Each relative
         path is appended to the each profile of
-        <option>environment.profiles</option> to form the content of
+        {option}`environment.profiles` to form the content of
         the corresponding environment variable.
 
         Also, these variables are merged into
-        <xref linkend="opt-environment.profileRelativeEnvVars"/> and it is
+        [](#opt-environment.profileRelativeEnvVars) and it is
         therefore not possible to use PAM style variables such as
-        <code>@{HOME}</code>.
+        `@{HOME}`.
       '';
     };
 
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index 875c4c9c4415..e8bbeac4f720 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -64,39 +64,40 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
-        description = ''
+        description = lib.mdDoc ''
           The set of packages that appear in
           /run/current-system/sw.  These packages are
           automatically available to all users, and are
           automatically updated every time you rebuild the system
           configuration.  (The latter is the main difference with
           installing them in the default profile,
-          <filename>/nix/var/nix/profiles/default</filename>.
+          {file}`/nix/var/nix/profiles/default`.
         '';
       };
 
       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.
@@ -109,20 +110,20 @@ in
         # to work.
         default = [];
         example = ["/"];
-        description = "List of directories to be symlinked in <filename>/run/current-system/sw</filename>.";
+        description = lib.mdDoc "List of directories to be symlinked in {file}`/run/current-system/sw`.";
       };
 
       extraOutputsToInstall = mkOption {
         type = types.listOf types.str;
         default = [ ];
         example = [ "doc" "info" "devdoc" ];
-        description = "List of additional package outputs to be symlinked into <filename>/run/current-system/sw</filename>.";
+        description = lib.mdDoc "List of additional package outputs to be symlinked into {file}`/run/current-system/sw`.";
       };
 
       extraSetup = mkOption {
         type = types.lines;
         default = "";
-        description = "Shell fragments to be run after the system environment has been created. This should only be used for things that need to modify the internals of the environment, e.g. generating MIME caches. The environment being built can be accessed at $out.";
+        description = lib.mdDoc "Shell fragments to be run after the system environment has been created. This should only be used for things that need to modify the internals of the environment, e.g. generating MIME caches. The environment being built can be accessed at $out.";
       };
 
     };
@@ -131,7 +132,7 @@ in
 
       path = mkOption {
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           The packages you want in the boot environment.
         '';
       };
diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix
index 693404a429c3..82f9ae48372a 100644
--- a/nixos/modules/config/terminfo.nix
+++ b/nixos/modules/config/terminfo.nix
@@ -9,7 +9,7 @@ with lib;
   options.environment.enableAllTerminfo = with lib; mkOption {
     default = false;
     type = types.bool;
-    description = ''
+    description = lib.mdDoc ''
       Whether to install all terminfo outputs
     '';
   };
diff --git a/nixos/modules/config/unix-odbc-drivers.nix b/nixos/modules/config/unix-odbc-drivers.nix
index 055c3b2364e6..7bd3fa1600b0 100644
--- a/nixos/modules/config/unix-odbc-drivers.nix
+++ b/nixos/modules/config/unix-odbc-drivers.nix
@@ -20,10 +20,10 @@ in {
       type = types.listOf types.package;
       default = [];
       example = literalExpression "with pkgs.unixODBCDrivers; [ sqlite psql ]";
-      description = ''
+      description = lib.mdDoc ''
         Specifies Unix ODBC drivers to be registered in
-        <filename>/etc/odbcinst.ini</filename>.  You may also want to
-        add <literal>pkgs.unixODBC</literal> to the system path to get
+        {file}`/etc/odbcinst.ini`.  You may also want to
+        add `pkgs.unixODBC` to the system path to get
         a command line client to connect to ODBC databases.
       '';
     };
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 26ce561013b6..4368ec24ea9e 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -186,7 +186,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");
 
@@ -223,10 +223,10 @@ foreach my $u (@{$spec->{users}}) {
     }
 
     # Ensure home directory incl. ownership and permissions.
-    if ($u->{createHome}) {
-        make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home} and ! $is_dry;
+    if ($u->{createHome} and !$is_dry) {
+        make_path($u->{home}, { mode => oct($u->{homeMode}) }) if ! -e $u->{home};
         chown $u->{uid}, $u->{gid}, $u->{home};
-        chmod 0700, $u->{home};
+        chmod oct($u->{homeMode}), $u->{home};
     }
 
     if (defined $u->{passwordFile}) {
@@ -272,7 +272,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/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index b0f96c754fa5..61d70ccc19b2 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -6,12 +6,6 @@ let
   ids = config.ids;
   cfg = config.users;
 
-  isPasswdCompatible = str: !(hasInfix ":" str || hasInfix "\n" str);
-  passwdEntry = type: lib.types.addCheck type isPasswdCompatible // {
-    name = "passwdEntry ${type.name}";
-    description = "${type.description}, not containing newlines or colons";
-  };
-
   # Check whether a password hash will allow login.
   allowsLogin = hash:
     hash == "" # login without password
@@ -23,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 ca 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).
   '';
 
@@ -60,29 +54,29 @@ let
     options = {
 
       name = mkOption {
-        type = passwdEntry types.str;
+        type = types.passwdEntry types.str;
         apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
-        description = ''
+        description = lib.mdDoc ''
           The name of the user account. If undefined, the name of the
           attribute set will be used.
         '';
       };
 
       description = mkOption {
-        type = passwdEntry types.str;
+        type = types.passwdEntry types.str;
         default = "";
         example = "Alice Q. User";
-        description = ''
+        description = lib.mdDoc ''
           A short description of the user account, typically the
           user's full name.  This is actually the “GECOS” or “comment”
-          field in <filename>/etc/passwd</filename>.
+          field in {file}`/etc/passwd`.
         '';
       };
 
       uid = mkOption {
         type = with types; nullOr int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The account UID. If the UID is null, a free UID is picked on
           activation.
         '';
@@ -91,32 +85,32 @@ let
       isSystemUser = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Indicates if the user is a system user or not. This option
-          only has an effect if <option>uid</option> is
-          <option>null</option>, in which case it determines whether
+          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
           1000).
-          Exactly one of <literal>isNormalUser</literal> and
-          <literal>isSystemUser</literal> must be true.
+          Exactly one of `isNormalUser` and
+          `isSystemUser` must be true.
         '';
       };
 
       isNormalUser = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Indicates whether this is an account for a “real” user. This
-          automatically sets <option>group</option> to
-          <literal>users</literal>, <option>createHome</option> to
-          <literal>true</literal>, <option>home</option> to
-          <filename>/home/<replaceable>username</replaceable></filename>,
-          <option>useDefaultShell</option> to <literal>true</literal>,
-          and <option>isSystemUser</option> to
-          <literal>false</literal>.
-          Exactly one of <literal>isNormalUser</literal> and
-          <literal>isSystemUser</literal> must be true.
+          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.
         '';
       };
 
@@ -124,25 +118,31 @@ let
         type = types.str;
         apply = x: assert (builtins.stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
         default = "";
-        description = "The user's primary group.";
+        description = lib.mdDoc "The user's primary group.";
       };
 
       extraGroups = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "The user's auxiliary groups.";
+        description = lib.mdDoc "The user's auxiliary groups.";
       };
 
       home = mkOption {
-        type = passwdEntry types.path;
+        type = types.passwdEntry types.path;
         default = "/var/empty";
-        description = "The user's home directory.";
+        description = lib.mdDoc "The user's home directory.";
+      };
+
+      homeMode = mkOption {
+        type = types.strMatching "[0-7]{1,5}";
+        default = "700";
+        description = lib.mdDoc "The user's home directory mode in numeric format. See chmod(1). The mode is only applied if {option}`users.users.<name>.createHome` is true.";
       };
 
       cryptHomeLuks = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to encrypted luks device that contains
           the user's home directory.
         '';
@@ -151,28 +151,27 @@ let
       pamMount = mkOption {
         type = with types; attrsOf str;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Attributes for user's entry in
-          <filename>pam_mount.conf.xml</filename>.
-          Useful attributes might include <code>path</code>,
-          <code>options</code>, <code>fstype</code>, and <code>server</code>.
-          See <link
-          xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />
+          {file}`pam_mount.conf.xml`.
+          Useful attributes might include `path`,
+          `options`, `fstype`, and `server`.
+          See <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>
           for more information.
         '';
       };
 
       shell = mkOption {
-        type = types.nullOr (types.either types.shellPackage (passwdEntry types.path));
+        type = types.nullOr (types.either types.shellPackage (types.passwdEntry types.path));
         default = pkgs.shadow;
         defaultText = literalExpression "pkgs.shadow";
         example = literalExpression "pkgs.bashInteractive";
-        description = ''
+        description = lib.mdDoc ''
           The path to the user's shell. Can use shell derivations,
-          like <literal>pkgs.bashInteractive</literal>. Don’t
+          like `pkgs.bashInteractive`. Don’t
           forget to enable your shell in
-          <literal>programs</literal> if necessary,
-          like <code>programs.zsh.enable = true;</code>.
+          `programs` if necessary,
+          like `programs.zsh.enable = true;`.
         '';
       };
 
@@ -183,10 +182,10 @@ let
           { startUid = 1000; count = 1; }
           { startUid = 100001; count = 65534; }
         ];
-        description = ''
+        description = lib.mdDoc ''
           Subordinate user ids that user is allowed to use.
-          They are set into <filename>/etc/subuid</filename> and are used
-          by <literal>newuidmap</literal> for user namespaces.
+          They are set into {file}`/etc/subuid` and are used
+          by `newuidmap` for user namespaces.
         '';
       };
 
@@ -197,10 +196,10 @@ let
           { startGid = 100; count = 1; }
           { startGid = 1001; count = 999; }
         ];
-        description = ''
+        description = lib.mdDoc ''
           Subordinate group ids that user is allowed to use.
-          They are set into <filename>/etc/subgid</filename> and are used
-          by <literal>newgidmap</literal> for user namespaces.
+          They are set into {file}`/etc/subgid` and are used
+          by `newgidmap` for user namespaces.
         '';
       };
 
@@ -208,7 +207,7 @@ let
         type = types.bool;
         default = false;
         example = true;
-        description = ''
+        description = lib.mdDoc ''
           Automatically allocate subordinate user and group ids for this user.
           Allocated range is currently always of size 65536.
         '';
@@ -217,7 +216,7 @@ let
       createHome = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create the home directory and ensure ownership as well as
           permissions to match the user.
         '';
@@ -226,16 +225,16 @@ let
       useDefaultShell = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If true, the user's shell will be set to
-          <option>users.defaultUserShell</option>.
+          {option}`users.defaultUserShell`.
         '';
       };
 
       hashedPassword = mkOption {
         type = with types; nullOr (passwdEntry str);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the hashed password for the user.
           ${passwordDescription}
           ${hashedPasswordDescription}
@@ -245,7 +244,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
@@ -257,11 +256,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}
         '';
       };
@@ -269,13 +268,13 @@ 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.
 
           ${hashedPasswordDescription}
         '';
@@ -284,13 +283,13 @@ let
       initialPassword = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the initial password for the user, i.e. the
           password assigned if the user does not already exist. If
-          <option>users.mutableUsers</option> is true, the password
+          {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>password</option>
+          {command}`passwd` command. Otherwise, it's
+          equivalent to setting the {option}`password`
           option. The same caveat applies: the password specified here
           is world-readable in the Nix store, so it should only be
           used for guest accounts or passwords that will be changed
@@ -302,9 +301,9 @@ let
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
-        description = ''
+        description = lib.mdDoc ''
           The set of packages that should be made available to the user.
-          This is in contrast to <option>environment.systemPackages</option>,
+          This is in contrast to {option}`environment.systemPackages`,
           which adds packages to all users.
         '';
       };
@@ -319,6 +318,7 @@ let
           group = mkDefault "users";
           createHome = mkDefault true;
           home = mkDefault "/home/${config.name}";
+          homeMode = mkDefault "700";
           useDefaultShell = mkDefault true;
           isSystemUser = mkDefault false;
         })
@@ -342,8 +342,8 @@ let
     options = {
 
       name = mkOption {
-        type = passwdEntry types.str;
-        description = ''
+        type = types.passwdEntry types.str;
+        description = lib.mdDoc ''
           The name of the group. If undefined, the name of the attribute set
           will be used.
         '';
@@ -352,7 +352,7 @@ let
       gid = mkOption {
         type = with types; nullOr int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The group GID. If the GID is null, a free GID is picked on
           activation.
         '';
@@ -361,9 +361,9 @@ let
       members = mkOption {
         type = with types; listOf (passwdEntry str);
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           The user names of the group members, added to the
-          <literal>/etc/group</literal> file.
+          `/etc/group` file.
         '';
       };
 
@@ -383,7 +383,7 @@ let
     options = {
       startUid = mkOption {
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Start of the range of subordinate user ids that user is
           allowed to use.
         '';
@@ -391,7 +391,7 @@ let
       count = mkOption {
         type = types.int;
         default = 1;
-        description = "Count of subordinate user ids";
+        description = lib.mdDoc "Count of subordinate user ids";
       };
     };
   };
@@ -400,7 +400,7 @@ let
     options = {
       startGid = mkOption {
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Start of the range of subordinate group ids that user is
           allowed to use.
         '';
@@ -408,7 +408,7 @@ let
       count = mkOption {
         type = types.int;
         default = 1;
-        description = "Count of subordinate group ids";
+        description = lib.mdDoc "Count of subordinate group ids";
       };
     };
   };
@@ -430,7 +430,7 @@ let
     inherit (cfg) mutableUsers;
     users = mapAttrsToList (_: u:
       { inherit (u)
-          name uid group description home createHome isSystemUser
+          name uid group description home homeMode createHome isSystemUser
           password passwordFile hashedPassword
           autoSubUidGidRange subUidRanges subGidRanges
           initialPassword initialHashedPassword;
@@ -458,32 +458,32 @@ 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.
+        :::
       '';
     };
 
     users.enforceIdUniqueness = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to require that no two users/groups share the same uid/gid.
       '';
     };
@@ -502,7 +502,7 @@ in {
           shell = "/bin/sh";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         Additional user accounts to be created automatically by the system.
         This can also be used to set options for root.
       '';
@@ -515,7 +515,7 @@ in {
           hackers = { };
         };
       type = with types; attrsOf (submodule groupOpts);
-      description = ''
+      description = lib.mdDoc ''
         Additional groups to be created automatically by the system.
       '';
     };
@@ -524,8 +524,8 @@ in {
     users.allowNoPasswordLogin = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Disable checking that at least the <literal>root</literal> user or a user in the <literal>wheel</literal> group can log in using
+      description = lib.mdDoc ''
+        Disable checking that at least the `root` user or a user in the `wheel` group can log in using
         a password or an SSH key.
 
         WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing.
@@ -592,13 +592,33 @@ in {
       '';
     };
 
+    # Warn about user accounts with deprecated password hashing schemes
+    system.activationScripts.hashes = {
+      deps = [ "users" ];
+      text = ''
+        users=()
+        while IFS=: read -r user hash tail; do
+          if [[ "$hash" = "$"* && ! "$hash" =~ ^\$(y|gy|7|2b|2y|2a|6)\$ ]]; then
+            users+=("$user")
+          fi
+        done </etc/shadow
+
+        if (( "''${#users[@]}" )); then
+          echo "
+        WARNING: The following user accounts rely on password hashes that will
+        be removed in NixOS 23.05. They should be renewed as soon as possible."
+          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,7 +626,7 @@ 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"
@@ -697,7 +717,7 @@ in {
           value = "[a-zA-Z0-9/+.-]+";
           options = "${id}(=${value})?(,${id}=${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/nixos/modules/config/vte.nix b/nixos/modules/config/vte.nix
index 24d32a00fd45..a969607f6e0b 100644
--- a/nixos/modules/config/vte.nix
+++ b/nixos/modules/config/vte.nix
@@ -25,7 +25,7 @@ in
     programs.bash.vteIntegration = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable Bash integration for VTE terminals.
         This allows it to preserve the current directory of the shell
         across terminals.
@@ -35,7 +35,7 @@ in
     programs.zsh.vteIntegration = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable Zsh integration for VTE terminals.
         This allows it to preserve the current directory of the shell
         across terminals.
diff --git a/nixos/modules/config/xdg/autostart.nix b/nixos/modules/config/xdg/autostart.nix
index 40984cb5ec53..a4fdbda911a2 100644
--- a/nixos/modules/config/xdg/autostart.nix
+++ b/nixos/modules/config/xdg/autostart.nix
@@ -10,9 +10,9 @@ with lib;
     xdg.autostart.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html">XDG Autostart specification</link>.
+        [XDG Autostart specification](https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html).
       '';
     };
   };
diff --git a/nixos/modules/config/xdg/icons.nix b/nixos/modules/config/xdg/icons.nix
index c83fdc251ef0..8d44a431445b 100644
--- a/nixos/modules/config/xdg/icons.nix
+++ b/nixos/modules/config/xdg/icons.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 {
@@ -10,9 +10,9 @@ with lib;
     xdg.icons.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html">XDG Icon Theme specification</link>.
+        [XDG Icon Theme specification](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html).
       '';
     };
   };
@@ -23,6 +23,12 @@ with lib;
       "/share/pixmaps"
     ];
 
+    environment.systemPackages = [
+      # Empty icon theme that contains index.theme file describing directories
+      # where toolkits should look for icons installed by apps.
+      pkgs.hicolor-icon-theme
+    ];
+
     # libXcursor looks for cursors in XCURSOR_PATH
     # it mostly follows the spec for icons
     # See: https://www.x.org/releases/current/doc/man/man3/Xcursor.3.xhtml Themes
diff --git a/nixos/modules/config/xdg/menus.nix b/nixos/modules/config/xdg/menus.nix
index 6735a7a5c430..b8f829e81547 100644
--- a/nixos/modules/config/xdg/menus.nix
+++ b/nixos/modules/config/xdg/menus.nix
@@ -10,9 +10,9 @@ with lib;
     xdg.menus.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html">XDG Desktop Menu specification</link>.
+        [XDG Desktop Menu specification](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html).
       '';
     };
   };
diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix
index 9b6dd4cab5f5..3aa863083219 100644
--- a/nixos/modules/config/xdg/mime.nix
+++ b/nixos/modules/config/xdg/mime.nix
@@ -18,10 +18,10 @@ in
     xdg.mime.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html">XDG Shared MIME-info specification</link> and the
-        <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html">XDG MIME Applications specification</link>.
+        [XDG Shared MIME-info specification](https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html) and the
+        [XDG MIME Applications specification](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html).
       '';
     };
 
@@ -32,10 +32,10 @@ in
         "application/pdf" = "firefox.desktop";
         "text/xml" = [ "nvim.desktop" "codium.desktop" ];
       };
-      description = ''
+      description = lib.mdDoc ''
         Adds associations between mimetypes and applications. See the
-        <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations">
-        specifications</link> for more information.
+        [
+        specifications](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations) for more information.
       '';
     };
 
@@ -46,10 +46,10 @@ in
         "application/pdf" = "firefox.desktop";
         "image/png" = [ "sxiv.desktop" "gimp.desktop" ];
       };
-      description = ''
+      description = lib.mdDoc ''
         Sets the default applications for given mimetypes. See the
-        <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#default">
-        specifications</link> for more information.
+        [
+        specifications](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#default) for more information.
       '';
     };
 
@@ -60,10 +60,10 @@ in
         "audio/mp3" = [ "mpv.desktop" "umpv.desktop" ];
         "inode/directory" = "codium.desktop";
       };
-      description = ''
+      description = lib.mdDoc ''
         Removes associations between mimetypes and applications. See the
-        <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations">
-        specifications</link> for more information.
+        [
+        specifications](https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html#associations) for more information.
       '';
     };
   };
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
index 088f2af59e22..ab6cffe499aa 100644
--- a/nixos/modules/config/xdg/portal.nix
+++ b/nixos/modules/config/xdg/portal.nix
@@ -1,10 +1,30 @@
 { config, pkgs, lib, ... }:
 
-with lib;
+let
+  inherit (lib)
+    mkEnableOption
+    mkIf
+    mkOption
+    mkRenamedOptionModule
+    teams
+    types;
+in
 
 {
   imports = [
     (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ])
+
+    ({ config, lib, options, ... }:
+      let
+        from = [ "xdg" "portal" "gtkUsePortal" ];
+        fromOpt = lib.getAttrFromPath from options;
+      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."
+        ];
+      }
+    )
   ];
 
   meta = {
@@ -13,31 +33,43 @@ with lib;
 
   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.
       '';
     };
 
     gtkUsePortal = mkOption {
       type = types.bool;
+      visible = false;
+      default = false;
+      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 `false` to respect its opt-in nature.
+      '';
+    };
+
+    xdgOpenUsePortal = mkOption {
+      type = types.bool;
       default = false;
-      description = ''
-        Sets environment variable <literal>GTK_USE_PORTAL</literal> to <literal>1</literal>.
-        This is needed for packages ran outside Flatpak to respect and use XDG Desktop Portals.
-        For example, you'd need to set this for non-flatpak Firefox to use native filechoosers.
-        Defaults to <literal>false</literal> to respect its opt-in nature.
+      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.
       '';
     };
   };
@@ -74,6 +106,7 @@ with lib;
 
         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/nixos/modules/config/xdg/portals/lxqt.nix b/nixos/modules/config/xdg/portals/lxqt.nix
new file mode 100644
index 000000000000..18fcf3d81c02
--- /dev/null
+++ b/nixos/modules/config/xdg/portals/lxqt.nix
@@ -0,0 +1,49 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.xdg.portal.lxqt;
+
+in
+{
+  meta = {
+    maintainers = teams.lxqt.members;
+  };
+
+  options.xdg.portal.lxqt = {
+    enable = mkEnableOption (lib.mdDoc ''
+      the desktop portal for the LXQt desktop environment.
+
+      This will add the `lxqt.xdg-desktop-portal-lxqt`
+      package (with the extra Qt styles) into the
+      {option}`xdg.portal.extraPortals` option
+    '');
+
+    styles = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression ''[
+        pkgs.libsForQt5.qtstyleplugin-kvantum
+        pkgs.breeze-qt5
+        pkgs.qtcurve
+      ];
+      '';
+      description = lib.mdDoc ''
+        Extra Qt styles that will be available to the
+        `lxqt.xdg-desktop-portal-lxqt`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    xdg.portal = {
+      enable = true;
+      extraPortals = [
+        (pkgs.lxqt.xdg-desktop-portal-lxqt.override { extraQtStyles = cfg.styles; })
+      ];
+    };
+
+    environment.systemPackages = cfg.styles;
+  };
+}
diff --git a/nixos/modules/config/xdg/portals/wlr.nix b/nixos/modules/config/xdg/portals/wlr.nix
index aba1d8dbc00e..d84ae794e3bc 100644
--- a/nixos/modules/config/xdg/portals/wlr.nix
+++ b/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/nixos/modules/config/xdg/sounds.nix b/nixos/modules/config/xdg/sounds.nix
index 0b94f550929b..713d68131fc0 100644
--- a/nixos/modules/config/xdg/sounds.nix
+++ b/nixos/modules/config/xdg/sounds.nix
@@ -10,9 +10,9 @@ with lib;
     xdg.sounds.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install files to support the
-        <link xlink:href="https://www.freedesktop.org/wiki/Specifications/sound-theme-spec/">XDG Sound Theme specification</link>.
+        [XDG Sound Theme specification](https://www.freedesktop.org/wiki/Specifications/sound-theme-spec/).
       '';
     };
   };
diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix
index 1f513b7e4dae..87ac53a60b7e 100644
--- a/nixos/modules/config/zram.nix
+++ b/nixos/modules/config/zram.nix
@@ -40,21 +40,21 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable in-memory compressed devices and swap space provided by the zram
           kernel module.
-          See <link xlink:href="https://www.kernel.org/doc/Documentation/blockdev/zram.txt">
+          See [
             https://www.kernel.org/doc/Documentation/blockdev/zram.txt
-          </link>.
+          ](https://www.kernel.org/doc/Documentation/blockdev/zram.txt).
         '';
       };
 
       numDevices = mkOption {
         default = 1;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Number of zram devices to create. See also
-          <literal>zramSwap.swapDevices</literal>
+          `zramSwap.swapDevices`
         '';
       };
 
@@ -62,37 +62,38 @@ in
         default = null;
         example = 1;
         type = with types; nullOr int;
-        description = ''
+        description = lib.mdDoc ''
           Number of zram devices to be used as swap. Must be
-          <literal>&lt;= zramSwap.numDevices</literal>.
-          Default is same as <literal>zramSwap.numDevices</literal>, recommended is 1.
+          `<= zramSwap.numDevices`.
+          Default is same as `zramSwap.numDevices`, recommended is 1.
         '';
       };
 
       memoryPercent = mkOption {
         default = 50;
         type = types.int;
-        description = ''
-          Maximum amount of memory that can be used by the zram swap devices
+        description = lib.mdDoc ''
+          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 <literal>zramctl</literal> 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 {
         default = null;
         type = with types; nullOr int;
-        description = ''
-          Maximum total amount of memory (in bytes) that can be used by the zram
+        description = lib.mdDoc ''
+          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 {
         default = 5;
         type = types.int;
-        description = ''
+        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
           fill the zram swap devices before falling back to disk swap).
@@ -103,12 +104,12 @@ in
         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.
+        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`
         '';
       };
     };
@@ -131,6 +132,8 @@ in
       options zram num_devices=${toString cfg.numDevices}
     '';
 
+    boot.kernelParams = ["zram.num_devices=${toString cfg.numDevices}"];
+
     services.udev.extraRules = ''
       KERNEL=="zram[0-9]*", ENV{SYSTEMD_WANTS}="zram-init-%k.service", TAG+="systemd"
     '';
@@ -177,9 +180,9 @@ in
           serviceConfig = {
             Type = "oneshot";
             RemainAfterExit = true;
-            ExecStartPre = "${modprobe} -r zram";
-            ExecStart = "${modprobe} zram";
-            ExecStop = "${modprobe} -r zram";
+            ExecStartPre = "-${modprobe} -r zram";
+            ExecStart = "-${modprobe} zram";
+            ExecStop = "-${modprobe} -r zram";
           };
           restartTriggers = [
             cfg.numDevices
diff --git a/nixos/modules/hardware/acpilight.nix b/nixos/modules/hardware/acpilight.nix
index 2de448a265c7..d8d82b0e81a4 100644
--- a/nixos/modules/hardware/acpilight.nix
+++ b/nixos/modules/hardware/acpilight.nix
@@ -10,7 +10,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable acpilight.
           This will allow brightness control via xbacklight from users in the video group
         '';
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 5b60b17312f9..2d5a0007ff01 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -21,15 +21,16 @@ in {
     hardware.enableAllFirmware = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Turn on this option if you want to enable all the firmware.
       '';
     };
 
     hardware.enableRedistributableFirmware = mkOption {
-      default = false;
+      default = config.hardware.enableAllFirmware;
+      defaultText = lib.literalExpression "config.hardware.enableAllFirmware";
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Turn on this option if you want to enable all the firmware with a license allowing redistribution.
       '';
     };
@@ -37,7 +38,7 @@ in {
     hardware.wirelessRegulatoryDatabase = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Load the wireless regulatory database at boot.
       '';
     };
@@ -61,7 +62,7 @@ in {
         alsa-firmware
         sof-firmware
         libreelec-dvb-firmware
-      ] ++ optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) raspberrypiWirelessFirmware
+      ] ++ 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") [
@@ -71,7 +72,7 @@ in {
     })
     (mkIf cfg.enableAllFirmware {
       assertions = [{
-        assertion = !cfg.enableAllFirmware || (config.nixpkgs.config.allowUnfree or false);
+        assertion = !cfg.enableAllFirmware || config.nixpkgs.config.allowUnfree;
         message = ''
           the list of hardware.enableAllFirmware contains non-redistributable licensed firmware files.
             This requires nixpkgs.config.allowUnfree to be true.
@@ -82,9 +83,11 @@ in {
         broadcom-bt-firmware
         b43Firmware_5_1_138
         b43Firmware_6_30_163_46
-        b43FirmwareCutter
         xow_dongle-firmware
-      ] ++ optional pkgs.stdenv.hostPlatform.isx86 facetimehd-firmware;
+      ] ++ optionals pkgs.stdenv.hostPlatform.isx86 [
+        facetimehd-calibration
+        facetimehd-firmware
+      ];
     })
     (mkIf cfg.wirelessRegulatoryDatabase {
       hardware.firmware = [ pkgs.wireless-regdb ];
diff --git a/nixos/modules/hardware/bladeRF.nix b/nixos/modules/hardware/bladeRF.nix
index 35b74b8382e3..52a1f52024c8 100644
--- a/nixos/modules/hardware/bladeRF.nix
+++ b/nixos/modules/hardware/bladeRF.nix
@@ -12,7 +12,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables udev rules for BladeRF devices. By default grants access
         to users in the "bladerf" group. You may want to install the
         libbladeRF package.
diff --git a/nixos/modules/hardware/brillo.nix b/nixos/modules/hardware/brillo.nix
index e970c9480998..612061718fad 100644
--- a/nixos/modules/hardware/brillo.nix
+++ b/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/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
index b2bbd77c9d7f..79977939eec8 100644
--- a/nixos/modules/hardware/ckb-next.nix
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -13,13 +13,13 @@ 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;
         default = null;
         example = 100;
-        description = ''
+        description = lib.mdDoc ''
           Limit access to the ckb daemon to a particular group.
         '';
       };
@@ -28,7 +28,7 @@ in
         type = types.package;
         default = pkgs.ckb-next;
         defaultText = literalExpression "pkgs.ckb-next";
-        description = ''
+        description = lib.mdDoc ''
           The package implementing the Corsair keyboard/mouse driver.
         '';
       };
@@ -48,6 +48,6 @@ in
     };
 
     meta = {
-      maintainers = with lib.maintainers; [ kierdavis ];
+      maintainers = with lib.maintainers; [ ];
     };
   }
diff --git a/nixos/modules/hardware/corectrl.nix b/nixos/modules/hardware/corectrl.nix
index 3185f6486c71..965cbe0267e0 100644
--- a/nixos/modules/hardware/corectrl.nix
+++ b/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/nixos/modules/hardware/cpu/amd-microcode.nix b/nixos/modules/hardware/cpu/amd-microcode.nix
index 621c7066bfe1..3f52cb1fca3e 100644
--- a/nixos/modules/hardware/cpu/amd-microcode.nix
+++ b/nixos/modules/hardware/cpu/amd-microcode.nix
@@ -11,7 +11,7 @@ with lib;
     hardware.cpu.amd.updateMicrocode = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Update the CPU microcode for AMD processors.
       '';
     };
diff --git a/nixos/modules/hardware/cpu/amd-sev.nix b/nixos/modules/hardware/cpu/amd-sev.nix
new file mode 100644
index 000000000000..28ee07f005ba
--- /dev/null
+++ b/nixos/modules/hardware/cpu/amd-sev.nix
@@ -0,0 +1,51 @@
+{ config, lib, ... }:
+with lib;
+let
+  cfg = config.hardware.cpu.amd.sev;
+  defaultGroup = "sev";
+in
+  with lib; {
+    options.hardware.cpu.amd.sev = {
+      enable = mkEnableOption (lib.mdDoc "access to the AMD SEV device");
+      user = mkOption {
+        description = lib.mdDoc "Owner to assign to the SEV device.";
+        type = types.str;
+        default = "root";
+      };
+      group = mkOption {
+        description = lib.mdDoc "Group to assign to the SEV device.";
+        type = types.str;
+        default = defaultGroup;
+      };
+      mode = mkOption {
+        description = lib.mdDoc "Mode to set for the SEV device.";
+        type = types.str;
+        default = "0660";
+      };
+    };
+
+    config = mkIf cfg.enable {
+      assertions = [
+        {
+          assertion = hasAttr cfg.user config.users.users;
+          message = "Given user does not exist";
+        }
+        {
+          assertion = (cfg.group == defaultGroup) || (hasAttr cfg.group config.users.groups);
+          message = "Given group does not exist";
+        }
+      ];
+
+      boot.extraModprobeConfig = ''
+        options kvm_amd sev=1
+      '';
+
+      users.groups = optionalAttrs (cfg.group == defaultGroup) {
+        "${cfg.group}" = {};
+      };
+
+      services.udev.extraRules = with cfg; ''
+        KERNEL=="sev", OWNER="${user}", GROUP="${group}", MODE="${mode}"
+      '';
+    };
+  }
diff --git a/nixos/modules/hardware/cpu/intel-microcode.nix b/nixos/modules/hardware/cpu/intel-microcode.nix
index acce565fd808..d30ebfefeeac 100644
--- a/nixos/modules/hardware/cpu/intel-microcode.nix
+++ b/nixos/modules/hardware/cpu/intel-microcode.nix
@@ -11,7 +11,7 @@ with lib;
     hardware.cpu.intel.updateMicrocode = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Update the CPU microcode for Intel processors.
       '';
     };
diff --git a/nixos/modules/hardware/cpu/intel-sgx.nix b/nixos/modules/hardware/cpu/intel-sgx.nix
index 1355ee753f0d..38a484cb126e 100644
--- a/nixos/modules/hardware/cpu/intel-sgx.nix
+++ b/nixos/modules/hardware/cpu/intel-sgx.nix
@@ -6,13 +6,13 @@ let
 in
 {
   options.hardware.cpu.intel.sgx.enableDcapCompat = mkOption {
-    description = ''
+    description = lib.mdDoc ''
       Whether to enable backward compatibility for SGX software build for the
       out-of-tree Intel SGX DCAP driver.
 
-      Creates symbolic links for the SGX devices <literal>/dev/sgx_enclave</literal>
-      and <literal>/dev/sgx_provision</literal> to make them available as
-      <literal>/dev/sgx/enclave</literal>  and <literal>/dev/sgx/provision</literal>,
+      Creates symbolic links for the SGX devices `/dev/sgx_enclave`
+      and `/dev/sgx_provision` to make them available as
+      `/dev/sgx/enclave`  and `/dev/sgx/provision`,
       respectively.
     '';
     type = types.bool;
@@ -20,19 +20,19 @@ 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 = "Owner to assign to the SGX provisioning device.";
+      description = lib.mdDoc "Owner to assign to the SGX provisioning device.";
       type = types.str;
       default = "root";
     };
     group = mkOption {
-      description = "Group to assign to the SGX provisioning device.";
+      description = lib.mdDoc "Group to assign to the SGX provisioning device.";
       type = types.str;
       default = defaultPrvGroup;
     };
     mode = mkOption {
-      description = "Mode to set for the SGX provisioning device.";
+      description = lib.mdDoc "Mode to set for the SGX provisioning device.";
       type = types.str;
       default = "0660";
     };
diff --git a/nixos/modules/hardware/device-tree.nix b/nixos/modules/hardware/device-tree.nix
index be67116ad507..2807313a5a9c 100644
--- a/nixos/modules/hardware/device-tree.nix
+++ b/nixos/modules/hardware/device-tree.nix
@@ -9,14 +9,23 @@ let
     options = {
       name = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Name of this overlay
         '';
       };
 
+      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 = ''
+        description = lib.mdDoc ''
           Path to .dts overlay file, overlay is applied to
           each .dtb file matching "compatible" of the overlay.
         '';
@@ -27,7 +36,7 @@ let
       dtsText = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Literal DTS contents, overlay is applied to
           each .dtb file matching "compatible" of the overlay.
         '';
@@ -36,14 +45,11 @@ let
           /plugin/;
           / {
                   compatible = "raspberrypi";
-                  fragment@0 {
-                          target-path = "/soc";
-                          __overlay__ {
-                                  pps {
-                                          compatible = "pps-gpio";
-                                          status = "okay";
-                                  };
-                          };
+          };
+          &{/soc} {
+                  pps {
+                          compatible = "pps-gpio";
+                          status = "okay";
                   };
           };
         '';
@@ -52,30 +58,13 @@ let
       dtboFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to .dtbo compiled overlay file.
         '';
       };
     };
   };
 
-  # 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
     then "${src}/dtbs"
     else
@@ -86,15 +75,18 @@ 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({ dtc }: pkgs.stdenv.mkDerivation {
+  compileDTS = name: f: pkgs.callPackage({ stdenv, dtc }: stdenv.mkDerivation {
     name = "${name}-dtbo";
 
     nativeBuildInputs = [ dtc ];
 
     buildCommand = ''
-      dtc -I dts ${f} -O dtb -@ -o $out
+      $CC -E -nostdinc -I${getDev cfg.kernelPackage}/lib/modules/${cfg.kernelPackage.modDirVersion}/source/scripts/dtc/include-prefixes -undef -D__DTS__ -x assembler-with-cpp ${f} | \
+        dtc -I dts -O dtb -@ -o $out
     '';
   }) {};
 
@@ -117,7 +109,7 @@ in
         enable = mkOption {
           default = pkgs.stdenv.hostPlatform.linux-kernel.DTB or false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Build device tree files. These are used to describe the
             non-discoverable hardware of a system.
           '';
@@ -128,7 +120,7 @@ in
           defaultText = literalExpression "config.boot.kernelPackages.kernel";
           example = literalExpression "pkgs.linux_latest";
           type = types.path;
-          description = ''
+          description = lib.mdDoc ''
             Kernel package containing the base device-tree (.dtb) to boot. Uses
             device trees bundled with the Linux kernel by default.
           '';
@@ -138,7 +130,7 @@ in
           default = null;
           example = "some-dtb.dtb";
           type = types.nullOr types.str;
-          description = ''
+          description = lib.mdDoc ''
             The name of an explicit dtb to be loaded, relative to the dtb base.
             Useful in extlinux scenarios if the bootloader doesn't pick the
             right .dtb file from FDTDIR.
@@ -149,7 +141,7 @@ in
           type = types.nullOr types.str;
           default = null;
           example = "*rpi*.dtb";
-          description = ''
+          description = lib.mdDoc ''
             Only include .dtb files matching glob expression.
           '';
         };
@@ -167,9 +159,10 @@ in
           '';
           type = types.listOf (types.coercedTo types.path (path: {
             name = baseNameOf path;
+            filter = null;
             dtboFile = path;
           }) overlayType);
-          description = ''
+          description = lib.mdDoc ''
             List of overlays to apply to base device-tree (.dtb) files.
           '';
         };
@@ -178,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`.
           '';
         };
@@ -199,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/nixos/modules/hardware/digitalbitbox.nix b/nixos/modules/hardware/digitalbitbox.nix
index 097448a74f4d..74e46bd34ace 100644
--- a/nixos/modules/hardware/digitalbitbox.nix
+++ b/nixos/modules/hardware/digitalbitbox.nix
@@ -11,7 +11,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables udev rules for Digital Bitbox devices.
       '';
     };
@@ -20,7 +20,7 @@ in
       type = types.package;
       default = pkgs.digitalbitbox;
       defaultText = literalExpression "pkgs.digitalbitbox";
-      description = "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
+      description = lib.mdDoc "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
     };
   };
 
diff --git a/nixos/modules/hardware/flirc.nix b/nixos/modules/hardware/flirc.nix
index 94ec715b9fa5..2fe40db947e4 100644
--- a/nixos/modules/hardware/flirc.nix
+++ b/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/nixos/modules/hardware/gkraken.nix b/nixos/modules/hardware/gkraken.nix
index 97d15369db0a..f427fec0a7cc 100644
--- a/nixos/modules/hardware/gkraken.nix
+++ b/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/nixos/modules/hardware/gpgsmartcards.nix b/nixos/modules/hardware/gpgsmartcards.nix
index 6e5fcda6b851..68e1e5f74e2e 100644
--- a/nixos/modules/hardware/gpgsmartcards.nix
+++ b/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 {
@@ -19,7 +19,7 @@ let
   # per debian's udev deb hook (https://man7.org/linux/man-pages/man1/dh_installudev.1.html)
   destination = "60-scdaemon.rules";
 
-  scdaemonUdevRulesPkg = pkgs.runCommandNoCC "scdaemon-udev-rules" {} ''
+  scdaemonUdevRulesPkg = pkgs.runCommand "scdaemon-udev-rules" {} ''
     loc="$out/lib/udev/rules.d/"
     mkdir -p "''${loc}"
     cp "${scdaemonRules}" "''${loc}/${destination}"
@@ -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/nixos/modules/hardware/hackrf.nix b/nixos/modules/hardware/hackrf.nix
index 7f03b765bbda..38ef7fa6d3d4 100644
--- a/nixos/modules/hardware/hackrf.nix
+++ b/nixos/modules/hardware/hackrf.nix
@@ -9,7 +9,7 @@ in
     enable = lib.mkOption {
       type = lib.types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables hackrf udev rules and ensures 'plugdev' group exists.
         This is a prerequisite to using HackRF devices without being root, since HackRF USB descriptors will be owned by plugdev through udev.
       '';
diff --git a/nixos/modules/hardware/i2c.nix b/nixos/modules/hardware/i2c.nix
index ff14b4b1c891..c0423cc5d997 100644
--- a/nixos/modules/hardware/i2c.nix
+++ b/nixos/modules/hardware/i2c.nix
@@ -8,16 +8,16 @@ 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;
       default = "i2c";
-      description = ''
+      description = lib.mdDoc ''
         Grant access to i2c devices (/dev/i2c-*) to users in this group.
       '';
     };
diff --git a/nixos/modules/hardware/keyboard/teck.nix b/nixos/modules/hardware/keyboard/teck.nix
index 091ddb81962e..2705668d9a75 100644
--- a/nixos/modules/hardware/keyboard/teck.nix
+++ b/nixos/modules/hardware/keyboard/teck.nix
@@ -6,7 +6,7 @@ let
 in
 {
   options.hardware.keyboard.teck = {
-    enable = mkEnableOption "non-root access to the firmware of TECK keyboards";
+    enable = mkEnableOption (lib.mdDoc "non-root access to the firmware of TECK keyboards");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/hardware/keyboard/uhk.nix b/nixos/modules/hardware/keyboard/uhk.nix
new file mode 100644
index 000000000000..c18051439938
--- /dev/null
+++ b/nixos/modules/hardware/keyboard/uhk.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.hardware.keyboard.uhk;
+in
+{
+  options.hardware.keyboard.uhk = {
+    enable = mkEnableOption (lib.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.
+    '');
+
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.uhk-udev-rules ];
+  };
+}
diff --git a/nixos/modules/hardware/keyboard/zsa.nix b/nixos/modules/hardware/keyboard/zsa.nix
index bb69cfa0bf09..5bf4022cdc43 100644
--- a/nixos/modules/hardware/keyboard/zsa.nix
+++ b/nixos/modules/hardware/keyboard/zsa.nix
@@ -9,7 +9,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      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.
diff --git a/nixos/modules/hardware/ksm.nix b/nixos/modules/hardware/ksm.nix
index 829c3532c459..82d94e6ab57c 100644
--- a/nixos/modules/hardware/ksm.nix
+++ b/nixos/modules/hardware/ksm.nix
@@ -11,13 +11,13 @@ 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;
-      description = ''
+      description = lib.mdDoc ''
         How many milliseconds ksmd should sleep between scans.
-        Setting it to <literal>null</literal> uses the kernel's default time.
+        Setting it to `null` uses the kernel's default time.
       '';
     };
   };
diff --git a/nixos/modules/hardware/ledger.nix b/nixos/modules/hardware/ledger.nix
index 41abe74315a0..fcce4f61a870 100644
--- a/nixos/modules/hardware/ledger.nix
+++ b/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/nixos/modules/hardware/logitech.nix b/nixos/modules/hardware/logitech.nix
index 3ebe6aacf5d6..9b06eb8a8b01 100644
--- a/nixos/modules/hardware/logitech.nix
+++ b/nixos/modules/hardware/logitech.nix
@@ -19,12 +19,12 @@ in
   options.hardware.logitech = {
 
     lcd = {
-      enable = mkEnableOption "Logitech LCD Devices";
+      enable = mkEnableOption (lib.mdDoc "Logitech LCD Devices");
 
       startWhenNeeded = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Only run the service when an actual supported device is plugged.
         '';
       };
@@ -32,22 +32,21 @@ in
       devices = mkOption {
         type = types.listOf types.str;
         default = [ "0a07" "c222" "c225" "c227" "c251" ];
-        description = ''
+        description = lib.mdDoc ''
           List of USB device ids supported by g15daemon.
-          </para>
-          <para>
+
           You most likely do not need to change this.
         '';
       };
     };
 
     wireless = {
-      enable = mkEnableOption "Logitech Wireless Devices";
+      enable = mkEnableOption (lib.mdDoc "Logitech Wireless Devices");
 
       enableGraphical = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable graphical support applications.";
+        description = lib.mdDoc "Enable graphical support applications.";
       };
     };
   };
diff --git a/nixos/modules/hardware/mcelog.nix b/nixos/modules/hardware/mcelog.nix
index 13ad238870c2..be8fc8cd1925 100644
--- a/nixos/modules/hardware/mcelog.nix
+++ b/nixos/modules/hardware/mcelog.nix
@@ -10,7 +10,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the Machine Check Exception logger.
         '';
       };
diff --git a/nixos/modules/hardware/network/ath-user-regd.nix b/nixos/modules/hardware/network/ath-user-regd.nix
index b5ade5ed5010..a7f023d26ce7 100644
--- a/nixos/modules/hardware/network/ath-user-regd.nix
+++ b/nixos/modules/hardware/network/ath-user-regd.nix
@@ -14,7 +14,7 @@ in
   options.networking.wireless.athUserRegulatoryDomain = mkOption {
     default = false;
     type = types.bool;
-    description = ''
+    description = lib.mdDoc ''
       If enabled, sets the ATH_USER_REGD kernel config switch to true to
       disable the enforcement of EEPROM regulatory restrictions for ath
       drivers. Requires at least Linux ${linuxKernelMinVersion}.
diff --git a/nixos/modules/hardware/network/b43.nix b/nixos/modules/hardware/network/b43.nix
index eb03bf223ccf..7f045f7b70f9 100644
--- a/nixos/modules/hardware/network/b43.nix
+++ b/nixos/modules/hardware/network/b43.nix
@@ -13,7 +13,7 @@ let kernelVersion = config.boot.kernelPackages.kernel.version; in
     networking.enableB43Firmware = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Turn on this option if you want firmware for the NICs supported by the b43 module.
       '';
     };
diff --git a/nixos/modules/hardware/network/intel-2200bg.nix b/nixos/modules/hardware/network/intel-2200bg.nix
index 17b973474c93..e1ec8134129e 100644
--- a/nixos/modules/hardware/network/intel-2200bg.nix
+++ b/nixos/modules/hardware/network/intel-2200bg.nix
@@ -9,7 +9,7 @@
     networking.enableIntel2200BGFirmware = lib.mkOption {
       default = false;
       type = lib.types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Turn on this option if you want firmware for the Intel
         PRO/Wireless 2200BG to be loaded automatically.  This is
         required if you want to use this device.
diff --git a/nixos/modules/hardware/new-lg4ff.nix b/nixos/modules/hardware/new-lg4ff.nix
new file mode 100644
index 000000000000..fac376eb7a75
--- /dev/null
+++ b/nixos/modules/hardware/new-lg4ff.nix
@@ -0,0 +1,29 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.new-lg4ff;
+  kernelPackages = config.boot.kernelPackages;
+in {
+  options.hardware.new-lg4ff = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enables improved Linux module drivers for Logitech driving wheels.
+        This will replace the existing in-kernel hid-logitech modules.
+        Works most notably on the Logitech G25, G27, G29 and Driving Force (GT).
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    boot = {
+      extraModulePackages = [ kernelPackages.new-lg4ff ];
+      kernelModules = [ "hid-logitech-new" ];
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ matthiasbenaets ];
+}
diff --git a/nixos/modules/hardware/nitrokey.nix b/nixos/modules/hardware/nitrokey.nix
index baa07203118c..fa9dd4d6d8f9 100644
--- a/nixos/modules/hardware/nitrokey.nix
+++ b/nixos/modules/hardware/nitrokey.nix
@@ -13,7 +13,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables udev rules for Nitrokey devices. By default grants access
         to users in the "nitrokey" group. You may want to install the
         nitrokey-app package, depending on your device and needs.
diff --git a/nixos/modules/hardware/onlykey/default.nix b/nixos/modules/hardware/onlykey/default.nix
index 07358c8a8782..59e159dce482 100644
--- a/nixos/modules/hardware/onlykey/default.nix
+++ b/nixos/modules/hardware/onlykey/default.nix
@@ -12,7 +12,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable OnlyKey device (https://crp.to/p/) support.
         '';
       };
diff --git a/nixos/modules/hardware/opengl.nix b/nixos/modules/hardware/opengl.nix
index 0d8aaf734591..5a5d88d9a4e0 100644
--- a/nixos/modules/hardware/opengl.nix
+++ b/nixos/modules/hardware/opengl.nix
@@ -35,7 +35,7 @@ in
 
     hardware.opengl = {
       enable = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable OpenGL drivers. This is needed to enable
           OpenGL support in X11 systems, as well as for Wayland compositors
           like sway and Weston. It is enabled by default
@@ -51,7 +51,7 @@ in
       driSupport = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable accelerated OpenGL rendering through the
           Direct Rendering Interface (DRI).
         '';
@@ -60,18 +60,18 @@ in
       driSupport32Bit = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           On 64-bit systems, whether to support Direct Rendering for
           32-bit applications (such as Wine).  This is currently only
-          supported for the <literal>nvidia</literal> as well as
-          <literal>Mesa</literal>.
+          supported for the `nvidia` as well as
+          `Mesa`.
         '';
       };
 
       package = mkOption {
         type = types.package;
         internal = true;
-        description = ''
+        description = lib.mdDoc ''
           The package that provides the OpenGL implementation.
         '';
       };
@@ -79,9 +79,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.
         '';
       };
@@ -90,7 +90,7 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "with pkgs; [ vaapiIntel libvdpau-va-gl vaapiVdpau intel-ocl ]";
-        description = ''
+        description = lib.mdDoc ''
           Additional packages to add to OpenGL drivers. This can be used
           to add OpenCL drivers, VA-API/VDPAU drivers etc.
         '';
@@ -100,9 +100,9 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "with pkgs.pkgsi686Linux; [ vaapiIntel libvdpau-va-gl vaapiVdpau ]";
-        description = ''
+        description = lib.mdDoc ''
           Additional packages to add to 32-bit OpenGL drivers on
-          64-bit systems. Used when <option>driSupport32Bit</option> is
+          64-bit systems. Used when {option}`driSupport32Bit` is
           set. This can be used to add OpenCL drivers, VA-API/VDPAU drivers etc.
         '';
       };
@@ -111,11 +111,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.
         '';
       };
diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix
index bd9fc485e17e..aaa4000e758f 100644
--- a/nixos/modules/hardware/openrazer.nix
+++ b/nixos/modules/hardware/openrazer.nix
@@ -49,14 +49,14 @@ in
 {
   options = {
     hardware.openrazer = {
-      enable = mkEnableOption ''
+      enable = mkEnableOption (lib.mdDoc ''
         OpenRazer drivers and userspace daemon.
-      '';
+      '');
 
       verboseLogging = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable verbose logging. Logs debug messages.
         '';
       };
@@ -64,7 +64,7 @@ in
       syncEffectsEnabled = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Set the sync effects flag to true so any assignment of
           effects will work across devices.
         '';
@@ -73,7 +73,7 @@ in
       devicesOffOnScreensaver = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Turn off the devices when the systems screensaver kicks in.
         '';
       };
@@ -81,7 +81,7 @@ in
       mouseBatteryNotifier = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Mouse battery notifier.
         '';
       };
@@ -89,7 +89,7 @@ in
       keyStatistics = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Collects number of keypresses per hour per key used to
           generate a heatmap.
         '';
@@ -98,7 +98,7 @@ in
       users = mkOption {
         type = with types; listOf str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Usernames to be added to the "openrazer" group, so that they
           can start and interact with the OpenRazer userspace daemon.
         '';
@@ -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/nixos/modules/hardware/opentabletdriver.nix b/nixos/modules/hardware/opentabletdriver.nix
index caba934ebe77..6c5ca3d949e8 100644
--- a/nixos/modules/hardware/opentabletdriver.nix
+++ b/nixos/modules/hardware/opentabletdriver.nix
@@ -12,7 +12,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable OpenTabletDriver udev rules, user service and blacklist kernel
           modules known to conflict with OpenTabletDriver.
         '';
@@ -21,7 +21,7 @@ in
       blacklistedKernelModules = mkOption {
         type = types.listOf types.str;
         default = [ "hid-uclogic" "wacom" ];
-        description = ''
+        description = lib.mdDoc ''
           Blacklist of kernel modules known to conflict with OpenTabletDriver.
         '';
       };
@@ -30,7 +30,7 @@ in
         type = types.package;
         default = pkgs.opentabletdriver;
         defaultText = literalExpression "pkgs.opentabletdriver";
-        description = ''
+        description = lib.mdDoc ''
           OpenTabletDriver derivation to use.
         '';
       };
@@ -39,7 +39,7 @@ in
         enable = mkOption {
           default = true;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Whether to start OpenTabletDriver daemon as a systemd user service.
           '';
         };
diff --git a/nixos/modules/hardware/pcmcia.nix b/nixos/modules/hardware/pcmcia.nix
index aef35a28e54d..f7a5565d773e 100644
--- a/nixos/modules/hardware/pcmcia.nix
+++ b/nixos/modules/hardware/pcmcia.nix
@@ -20,7 +20,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable this option to support PCMCIA card.
         '';
       };
@@ -28,7 +28,7 @@ in
       firmware = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of firmware used to handle specific PCMCIA card.
         '';
       };
@@ -36,7 +36,7 @@ in
       config = mkOption {
         default = null;
         type = types.nullOr types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to the configuration file which maps the memory, IRQs
           and ports used by the PCMCIA hardware.
         '';
diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix
index ef07542950ba..85e3215127fd 100644
--- a/nixos/modules/hardware/printers.nix
+++ b/nixos/modules/hardware/printers.nix
@@ -30,17 +30,17 @@ in {
       ensureDefaultPrinter = mkOption {
         type = types.nullOr printerName;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Ensures the named printer is the default CUPS printer / printer queue.
         '';
       };
       ensurePrinters = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Will regularly ensure that the given CUPS printers are configured as declared here.
           If a printer's options are manually changed afterwards, they will be overwritten eventually.
           This option will never delete any printer, even if removed from this list.
-          You can check existing printers with <command>lpstat -s</command>
-          and remove printers with <command>lpadmin -x &lt;printer-name&gt;</command>.
+          You can check existing printers with {command}`lpstat -s`
+          and remove printers with {command}`lpadmin -x <printer-name>`.
           Printers not listed here can still be manually configured.
         '';
         default = [];
@@ -49,7 +49,7 @@ in {
             name = mkOption {
               type = printerName;
               example = "BrotherHL_Workroom";
-              description = ''
+              description = lib.mdDoc ''
                 Name of the printer / printer queue.
                 May contain any printable characters except "/", "#", and space.
               '';
@@ -58,7 +58,7 @@ in {
               type = types.nullOr types.str;
               default = null;
               example = "Workroom";
-              description = ''
+              description = lib.mdDoc ''
                 Optional human-readable location.
               '';
             };
@@ -66,7 +66,7 @@ in {
               type = types.nullOr types.str;
               default = null;
               example = "Brother HL-5140";
-              description = ''
+              description = lib.mdDoc ''
                 Optional human-readable description.
               '';
             };
@@ -76,9 +76,9 @@ in {
                 "ipp://printserver.local/printers/BrotherHL_Workroom"
                 "usb://HP/DESKJET%20940C?serial=CN16E6C364BH"
               '';
-              description = ''
+              description = lib.mdDoc ''
                 How to reach the printer.
-                <command>lpinfo -v</command> shows a list of supported device URIs and schemes.
+                {command}`lpinfo -v` shows a list of supported device URIs and schemes.
               '';
             };
             model = mkOption {
@@ -86,9 +86,9 @@ in {
               example = literalExpression ''
                 "gutenprint.''${lib.versions.majorMinor (lib.getVersion pkgs.gutenprint)}://brother-hl-5140/expert"
               '';
-              description = ''
+              description = lib.mdDoc ''
                 Location of the ppd driver file for the printer.
-                <command>lpinfo -m</command> shows a list of supported models.
+                {command}`lpinfo -m` shows a list of supported models.
               '';
             };
             ppdOptions = mkOption {
@@ -98,9 +98,9 @@ in {
                 Duplex = "DuplexNoTumble";
               };
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 Sets PPD options for the printer.
-                <command>lpoptions [-p printername] -l</command> shows suported PPD options for the given printer.
+                {command}`lpoptions [-p printername] -l` shows supported PPD options for the given printer.
               '';
             };
           };
diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix
index fa6f0b8fc84a..2934cd19a8c1 100644
--- a/nixos/modules/hardware/raid/hpsa.nix
+++ b/nixos/modules/hardware/raid/hpsa.nix
@@ -40,7 +40,7 @@ let
       homepage = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/";
       license = licenses.unfreeRedistributable;
       platforms = [ "x86_64-linux" ];
-      maintainers = with maintainers; [ volth ];
+      maintainers = with maintainers; [ ];
     };
   };
 in {
@@ -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/nixos/modules/hardware/rtl-sdr.nix b/nixos/modules/hardware/rtl-sdr.nix
index e85fc04e29bb..7f462005f157 100644
--- a/nixos/modules/hardware/rtl-sdr.nix
+++ b/nixos/modules/hardware/rtl-sdr.nix
@@ -8,7 +8,7 @@ in {
     enable = lib.mkOption {
       type = lib.types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables rtl-sdr udev rules, ensures 'plugdev' group exists, and blacklists DVB kernel modules.
         This is a prerequisite to using devices supported by rtl-sdr without being root, since rtl-sdr USB descriptors will be owned by plugdev through udev.
        '';
diff --git a/nixos/modules/hardware/saleae-logic.nix b/nixos/modules/hardware/saleae-logic.nix
new file mode 100644
index 000000000000..f144814a06b7
--- /dev/null
+++ b/nixos/modules/hardware/saleae-logic.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.hardware.saleae-logic;
+in
+{
+  options.hardware.saleae-logic = {
+    enable = lib.mkEnableOption (lib.mdDoc "udev rules for Saleae Logic devices");
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.saleae-logic-2;
+      defaultText = lib.literalExpression "pkgs.saleae-logic-2";
+      description = lib.mdDoc ''
+        Saleae Logic package to use.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.udev.packages = [ cfg.package ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ chivay ];
+}
diff --git a/nixos/modules/hardware/sata.nix b/nixos/modules/hardware/sata.nix
index 81592997d6e3..5330ba9268b5 100644
--- a/nixos/modules/hardware/sata.nix
+++ b/nixos/modules/hardware/sata.nix
@@ -36,12 +36,12 @@ 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;
       type = types.int;
-      description = ''
+      description = lib.mdDoc ''
         Set SCT Error Recovery Control timeout in deciseconds for use in RAID configurations.
 
         Values are as follows:
@@ -53,17 +53,17 @@ in
     };
 
     drives = mkOption {
-      description = "List of drives for which to configure the timeout.";
+      description = lib.mdDoc "List of drives for which to configure the timeout.";
       type = types.listOf
         (types.submodule {
           options = {
             name = mkOption {
-              description = "Drive name without the full path.";
+              description = lib.mdDoc "Drive name without the full path.";
               type = types.str;
             };
 
             idBy = mkOption {
-              description = "The method to identify the drive.";
+              description = lib.mdDoc "The method to identify the drive.";
               type = types.enum [ "path" "wwn" ];
               default = "path";
             };
diff --git a/nixos/modules/hardware/sensor/hddtemp.nix b/nixos/modules/hardware/sensor/hddtemp.nix
index df3f75e229a2..b69d012b4d09 100644
--- a/nixos/modules/hardware/sensor/hddtemp.nix
+++ b/nixos/modules/hardware/sensor/hddtemp.nix
@@ -30,7 +30,7 @@ in
   options = {
     hardware.sensor.hddtemp = {
       enable = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Enable this option to support HDD/SSD temperature sensors.
         '';
         type = types.bool;
@@ -38,24 +38,24 @@ in
       };
 
       drives = mkOption {
-        description = "List of drives to monitor. If you pass /dev/disk/by-path/* entries the symlinks will be resolved as hddtemp doesn't like names with colons.";
+        description = lib.mdDoc "List of drives to monitor. If you pass /dev/disk/by-path/* entries the symlinks will be resolved as hddtemp doesn't like names with colons.";
         type = types.listOf types.str;
       };
 
       unit = mkOption {
-        description = "Celcius or Fahrenheit";
+        description = lib.mdDoc "Celcius or Fahrenheit";
         type = types.enum [ "C" "F" ];
         default = "C";
       };
 
       dbEntries = mkOption {
-        description = "Additional DB entries";
+        description = lib.mdDoc "Additional DB entries";
         type = types.listOf types.str;
         default = [ ];
       };
 
       extraArgs = mkOption {
-        description = "Additional arguments passed to the daemon.";
+        description = lib.mdDoc "Additional arguments passed to the daemon.";
         type = types.listOf types.str;
         default = [ ];
       };
diff --git a/nixos/modules/hardware/sensor/iio.nix b/nixos/modules/hardware/sensor/iio.nix
index 8b3ba87a7d9c..6f7b1dc1f7f8 100644
--- a/nixos/modules/hardware/sensor/iio.nix
+++ b/nixos/modules/hardware/sensor/iio.nix
@@ -8,7 +8,7 @@ with lib;
   options = {
     hardware.sensor.iio = {
       enable = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Enable this option to support IIO sensors with iio-sensor-proxy.
 
           IIO sensors are used for orientation and ambient light
diff --git a/nixos/modules/hardware/steam-hardware.nix b/nixos/modules/hardware/steam-hardware.nix
index 6218c9ffbb9b..07edf6870390 100644
--- a/nixos/modules/hardware/steam-hardware.nix
+++ b/nixos/modules/hardware/steam-hardware.nix
@@ -13,7 +13,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable udev rules for Steam hardware such as the Steam Controller, other supported controllers and the HTC Vive";
+      description = lib.mdDoc "Enable udev rules for Steam hardware such as the Steam Controller, other supported controllers and the HTC Vive";
     };
   };
 
diff --git a/nixos/modules/hardware/system-76.nix b/nixos/modules/hardware/system-76.nix
index ca40ee0ebb37..3fb2c10a6e3b 100644
--- a/nixos/modules/hardware/system-76.nix
+++ b/nixos/modules/hardware/system-76.nix
@@ -57,13 +57,13 @@ 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;
         defaultText = literalExpression "config.${opt.enableAll}";
         example = true;
-        description = "Whether to enable the system76 firmware daemon";
+        description = lib.mdDoc "Whether to enable the system76 firmware daemon";
         type = types.bool;
       };
 
@@ -71,7 +71,7 @@ in {
         default = cfg.enableAll;
         defaultText = literalExpression "config.${opt.enableAll}";
         example = true;
-        description = "Whether to make the system76 out-of-tree kernel modules available";
+        description = lib.mdDoc "Whether to make the system76 out-of-tree kernel modules available";
         type = types.bool;
       };
 
@@ -79,7 +79,7 @@ in {
         default = cfg.enableAll;
         defaultText = literalExpression "config.${opt.enableAll}";
         example = true;
-        description = "Whether to enable the system76 power daemon";
+        description = lib.mdDoc "Whether to enable the system76 power daemon";
         type = types.bool;
       };
     };
diff --git a/nixos/modules/hardware/tuxedo-keyboard.nix b/nixos/modules/hardware/tuxedo-keyboard.nix
index 97af7c61f3c9..3ae876bd1f18 100644
--- a/nixos/modules/hardware/tuxedo-keyboard.nix
+++ b/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/nixos/modules/hardware/ubertooth.nix b/nixos/modules/hardware/ubertooth.nix
index 637fddfb37df..e2db2068d900 100644
--- a/nixos/modules/hardware/ubertooth.nix
+++ b/nixos/modules/hardware/ubertooth.nix
@@ -10,13 +10,13 @@ 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;
       default = "ubertooth";
       example = "wheel";
-      description = "Group for Ubertooth's udev rules.";
+      description = lib.mdDoc "Group for Ubertooth's udev rules.";
     };
   };
 
diff --git a/nixos/modules/hardware/uinput.nix b/nixos/modules/hardware/uinput.nix
index 55e86bfa6bdb..15fa66b8d83c 100644
--- a/nixos/modules/hardware/uinput.nix
+++ b/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/nixos/modules/hardware/usb-storage.nix b/nixos/modules/hardware/usb-storage.nix
new file mode 100644
index 000000000000..9c1b7a125fd1
--- /dev/null
+++ b/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/nixos/modules/hardware/usb-wwan.nix b/nixos/modules/hardware/usb-wwan.nix
index 679a6c6497cb..69673872cf9b 100644
--- a/nixos/modules/hardware/usb-wwan.nix
+++ b/nixos/modules/hardware/usb-wwan.nix
@@ -11,7 +11,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable this option to support USB WWAN adapters.
         '';
       };
diff --git a/nixos/modules/hardware/video/bumblebee.nix b/nixos/modules/hardware/video/bumblebee.nix
index b6af4f80445a..75f71d499e66 100644
--- a/nixos/modules/hardware/video/bumblebee.nix
+++ b/nixos/modules/hardware/video/bumblebee.nix
@@ -29,7 +29,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable the bumblebee daemon to manage Optimus hybrid video cards.
           This should power off secondary GPU until its use is requested
           by running an application with optirun.
@@ -40,13 +40,13 @@ in
         default = "wheel";
         example = "video";
         type = types.str;
-        description = "Group for bumblebee socket";
+        description = lib.mdDoc "Group for bumblebee socket";
       };
 
       connectDisplay = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Set to true if you intend to connect your discrete card to a
           monitor. This option will set up your Nvidia card for EDID
           discovery and to turn on the monitor signal.
@@ -58,7 +58,7 @@ in
       driver = mkOption {
         default = "nvidia";
         type = types.enum [ "nvidia" "nouveau" ];
-        description = ''
+        description = lib.mdDoc ''
           Set driver used by bumblebeed. Supported are nouveau and nvidia.
         '';
       };
@@ -66,7 +66,7 @@ in
       pmMethod = mkOption {
         default = "auto";
         type = types.enum [ "auto" "bbswitch" "switcheroo" "none" ];
-        description = ''
+        description = lib.mdDoc ''
           Set preferred power management method for unused card.
         '';
       };
diff --git a/nixos/modules/hardware/video/capture/mwprocapture.nix b/nixos/modules/hardware/video/capture/mwprocapture.nix
index 76cb4c6ee9bf..ddd3f3ec7f32 100644
--- a/nixos/modules/hardware/video/capture/mwprocapture.nix
+++ b/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/nixos/modules/hardware/video/hidpi.nix b/nixos/modules/hardware/video/hidpi.nix
index ac72b652504e..8c8f8bc0c265 100644
--- a/nixos/modules/hardware/video/hidpi.nix
+++ b/nixos/modules/hardware/video/hidpi.nix
@@ -2,7 +2,7 @@
 with lib;
 
 {
-  options.hardware.video.hidpi.enable = mkEnableOption "Font/DPI configuration optimized for HiDPI displays";
+  options.hardware.video.hidpi.enable = mkEnableOption (lib.mdDoc "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";
@@ -11,6 +11,14 @@ with lib;
     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/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 6899eb4e196a..cee230ac41cb 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -24,7 +24,7 @@ let
   primeEnabled = syncCfg.enable || offloadCfg.enable;
   nvidiaPersistencedEnabled =  cfg.nvidiaPersistenced;
   nvidiaSettings = cfg.nvidiaSettings;
-  busIDType = types.strMatching "([[:print:]]+\:[0-9]{1,3}\:[0-9]{1,2}\:[0-9])?";
+  busIDType = types.strMatching "([[:print:]]+[\:\@][0-9]{1,3}\:[0-9]{1,2}\:[0-9])?";
 in
 
 {
@@ -40,7 +40,7 @@ in
     hardware.nvidia.powerManagement.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Experimental power management through systemd. For more information, see
         the NVIDIA docs, on Chapter 21. Configuring Power Management Support.
       '';
@@ -49,7 +49,7 @@ in
     hardware.nvidia.powerManagement.finegrained = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Experimental power management of PRIME offload. For more information, see
         the NVIDIA docs, chapter 22. PCI-Express runtime power management.
       '';
@@ -58,11 +58,11 @@ in
     hardware.nvidia.modesetting.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable kernel modesetting when using the NVIDIA proprietary driver.
 
         Enabling this fixes screen tearing when using Optimus via PRIME (see
-        <option>hardware.nvidia.prime.sync.enable</option>. This is not enabled
+        {option}`hardware.nvidia.prime.sync.enable`. This is not enabled
         by default because it is not officially supported by NVIDIA and would not
         work with SLI.
       '';
@@ -72,7 +72,7 @@ in
       type = busIDType;
       default = "";
       example = "PCI:1:0:0";
-      description = ''
+      description = lib.mdDoc ''
         Bus ID of the NVIDIA GPU. You can find it using lspci; for example if lspci
         shows the NVIDIA GPU at "01:00.0", set this option to "PCI:1:0:0".
       '';
@@ -82,7 +82,7 @@ in
       type = busIDType;
       default = "";
       example = "PCI:0:2:0";
-      description = ''
+      description = lib.mdDoc ''
         Bus ID of the Intel GPU. You can find it using lspci; for example if lspci
         shows the Intel GPU at "00:02.0", set this option to "PCI:0:2:0".
       '';
@@ -92,7 +92,7 @@ in
       type = busIDType;
       default = "";
       example = "PCI:4:0:0";
-      description = ''
+      description = lib.mdDoc ''
         Bus ID of the AMD APU. You can find it using lspci; for example if lspci
         shows the AMD APU at "04:00.0", set this option to "PCI:4:0:0".
       '';
@@ -101,26 +101,26 @@ in
     hardware.nvidia.prime.sync.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      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.
 
         Note that this option only has any effect if the "nvidia" driver is specified
-        in <option>services.xserver.videoDrivers</option>, and it should preferably
+        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</option> and
-        <option>hardware.nvidia.prime.intelBusId</option>).
+        specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId`).
 
         If you enable this, you may want to also enable kernel modesetting for the
-        NVIDIA driver (<option>hardware.nvidia.modesetting.enable</option>) in order
+        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>
+        for which the {option}`services.xserver.displayManager.setupCommands`
         option is supported is used.
       '';
     };
@@ -128,7 +128,7 @@ in
     hardware.nvidia.prime.sync.allowExternalGpu = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Configure X to allow external NVIDIA GPUs when using optimus.
       '';
     };
@@ -136,19 +136,19 @@ in
     hardware.nvidia.prime.offload.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      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</option> and
-        <option>hardware.nvidia.prime.intelBusId</option>).
+        specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
+        {option}`hardware.nvidia.prime.intelBusId`).
       '';
     };
 
     hardware.nvidia.nvidiaSettings = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to add nvidia-settings, NVIDIA's GUI configuration tool, to
         systemPackages.
       '';
@@ -157,21 +157,40 @@ in
     hardware.nvidia.nvidiaPersistenced = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Update for NVIDA GPU headless mode, i.e. nvidia-persistenced. It ensures all
         GPUs stay awake even during headless mode.
       '';
     };
 
+    hardware.nvidia.forceFullCompositionPipeline = lib.mkOption {
+      default = false;
+      type = types.bool;
+      description = lib.mdDoc ''
+        Whether to force-enable the full composition pipeline.
+        This sometimes fixes screen tearing issues.
+        This has been reported to reduce the performance of some OpenGL applications and may produce issues in WebGL.
+        It also drastically increases the time the driver needs to clock down after load.
+      '';
+    };
+
     hardware.nvidia.package = lib.mkOption {
-      type = lib.types.package;
+      type = types.package;
       default = config.boot.kernelPackages.nvidiaPackages.stable;
       defaultText = literalExpression "config.boot.kernelPackages.nvidiaPackages.stable";
-      description = ''
+      description = lib.mdDoc ''
         The NVIDIA X11 derivation to use.
       '';
       example = literalExpression "config.boot.kernelPackages.nvidiaPackages.legacy_340";
     };
+
+    hardware.nvidia.open = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to use the open source kernel module
+      '';
+    };
   };
 
   config = let
@@ -214,11 +233,13 @@ 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.";
+      }
+
+      {
+        assertion = cfg.open -> (cfg.package ? open && cfg.package ? firmware);
+        message = "This version of NVIDIA driver does not provide a corresponding opensource kernel driver";
       }
     ];
 
@@ -236,13 +257,11 @@ 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;
-
     services.xserver.drivers = let
     in 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"''}
@@ -255,13 +274,18 @@ in
         ''
           BusID "${pCfg.nvidiaBusId}"
           ${optionalString syncCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
-          ${optionalString cfg.powerManagement.finegrained "Option \"NVreg_DynamicPowerManagement=0x02\""}
         '';
       screenSection =
         ''
           Option "RandRRotation" "on"
-          ${optionalString syncCfg.enable "Option \"AllowEmptyInitialConfiguration\""}
-        '';
+        '' + optionalString syncCfg.enable ''
+          Option "AllowEmptyInitialConfiguration"
+        '' + optionalString cfg.forceFullCompositionPipeline ''
+          Option         "metamodes" "nvidia-auto-select +0+0 {ForceFullCompositionPipeline=On}"
+          Option         "AllowIndirectGLXProtocol" "off"
+          Option         "TripleBuffer" "on"
+        ''
+        ;
     };
 
     services.xserver.serverLayoutSection = optionalString syncCfg.enable ''
@@ -348,7 +372,8 @@ in
       ++ optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
         "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
 
-    boot.extraModulePackages = [ nvidia_x11.bin ];
+    boot.extraModulePackages = if cfg.open then [ nvidia_x11.open ] else [ nvidia_x11.bin ];
+    hardware.firmware = lib.optional cfg.open nvidia_x11.firmware;
 
     # nvidia-uvm is required by CUDA applications.
     boot.kernelModules = [ "nvidia-uvm" ] ++
@@ -356,17 +381,19 @@ 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.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
+      ++ optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1";
 
     services.udev.extraRules =
       ''
         # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
         KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 255'"
+        KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'for i in $$(cat /proc/driver/nvidia/gpus/*/information | grep Minor | cut -d \  -f 4); do mknod -m 666 /dev/nvidia$${i} c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) $${i}; done'"
         KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 254'"
-        KERNEL=="card*", SUBSYSTEM=="drm", DRIVERS=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia%n c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) %n'"
         KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
-        KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
-      '' + optionalString cfg.powerManagement.finegrained ''
+        KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 1'"
+      '' + optionalString cfg.powerManagement.finegrained (
+      optionalString (versionOlder config.boot.kernelPackages.kernel.version "5.5") ''
         # Remove NVIDIA USB xHCI Host Controller devices, if present
         ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{remove}="1"
 
@@ -375,7 +402,7 @@ in
 
         # Remove NVIDIA Audio devices, if present
         ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{remove}="1"
-
+      '' + ''
         # Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
         ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
         ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
@@ -383,7 +410,7 @@ in
         # Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
         ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
         ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
-      '';
+      '');
 
     boot.extraModprobeConfig = mkIf cfg.powerManagement.finegrained ''
       options nvidia "NVreg_DynamicPowerManagement=0x02"
diff --git a/nixos/modules/hardware/video/switcheroo-control.nix b/nixos/modules/hardware/video/switcheroo-control.nix
index 199adb2ad8f5..982388f8e5f4 100644
--- a/nixos/modules/hardware/video/switcheroo-control.nix
+++ b/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/nixos/modules/hardware/video/uvcvideo/default.nix b/nixos/modules/hardware/video/uvcvideo/default.nix
index 338062cf69b7..6cfb8cc6ad29 100644
--- a/nixos/modules/hardware/video/uvcvideo/default.nix
+++ b/nixos/modules/hardware/video/uvcvideo/default.nix
@@ -22,27 +22,27 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to enable <command>uvcvideo</command> dynamic controls.
+        description = lib.mdDoc ''
+          Whether to enable {command}`uvcvideo` dynamic controls.
 
-          Note that enabling this brings the <command>uvcdynctrl</command> tool
+          Note that enabling this brings the {command}`uvcdynctrl` tool
           into your environment and register all dynamic controls from
-          specified <command>packages</command> to the <command>uvcvideo</command> driver.
+          specified {command}`packages` to the {command}`uvcvideo` driver.
         '';
       };
 
       packages = mkOption {
         type = types.listOf types.path;
         example = literalExpression "[ pkgs.tiscamera ]";
-        description = ''
-          List of packages containing <command>uvcvideo</command> dynamic controls
+        description = lib.mdDoc ''
+          List of packages containing {command}`uvcvideo` dynamic controls
           rules. All files found in
-          <filename><replaceable>pkg</replaceable>/share/uvcdynctrl/data</filename>
+          {file}`«pkg»/share/uvcdynctrl/data`
           will be included.
 
-          Note that these will serve as input to the <command>libwebcam</command>
-          package which through its own <command>udev</command> rule will register
-          the dynamic controls from specified packages to the <command>uvcvideo</command>
+          Note that these will serve as input to the {command}`libwebcam`
+          package which through its own {command}`udev` rule will register
+          the dynamic controls from specified packages to the {command}`uvcvideo`
           driver.
         '';
         apply = map getBin;
diff --git a/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix b/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix
index a808429c9996..8dadbd53b989 100644
--- a/nixos/modules/hardware/video/uvcvideo/uvcdynctrl-udev-rules.nix
+++ b/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/nixos/modules/hardware/video/webcam/facetimehd.nix b/nixos/modules/hardware/video/webcam/facetimehd.nix
index b13f103350e9..480c636aa0d9 100644
--- a/nixos/modules/hardware/video/webcam/facetimehd.nix
+++ b/nixos/modules/hardware/video/webcam/facetimehd.nix
@@ -12,7 +12,19 @@ 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;
+    example = true;
+    type = types.bool;
+    description = lib.mdDoc ''
+      Whether to include sensor calibration files for facetimehd.
+      This makes colors look much better but is experimental, see
+      <https://github.com/patjak/facetimehd/wiki/Extracting-the-sensor-calibration-files>
+      for details.
+    '';
+  };
 
   config = mkIf cfg.enable {
 
@@ -22,7 +34,8 @@ in
 
     boot.extraModulePackages = [ kernelPackages.facetimehd ];
 
-    hardware.firmware = [ pkgs.facetimehd-firmware ];
+    hardware.firmware = [ pkgs.facetimehd-firmware ]
+      ++ optional cfg.withCalibration pkgs.facetimehd-calibration;
 
     # unload module during suspend/hibernate as it crashes the whole system
     powerManagement.powerDownCommands = ''
diff --git a/nixos/modules/hardware/wooting.nix b/nixos/modules/hardware/wooting.nix
index ee550cbbf6b8..90d046d49f4e 100644
--- a/nixos/modules/hardware/wooting.nix
+++ b/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/nixos/modules/hardware/xone.nix b/nixos/modules/hardware/xone.nix
index 89690d8c6fb1..211d3fce8679 100644
--- a/nixos/modules/hardware/xone.nix
+++ b/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/nixos/modules/hardware/xpadneo.nix b/nixos/modules/hardware/xpadneo.nix
index dbc4ba212560..a66e81d8b15b 100644
--- a/nixos/modules/hardware/xpadneo.nix
+++ b/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 {
@@ -15,7 +15,8 @@ in
       # https://wiki.archlinux.org/index.php/Gamepad#Connect_Xbox_Wireless_Controller_with_Bluetooth
       extraModprobeConfig =
         mkIf
-          config.hardware.bluetooth.enable
+          (config.hardware.bluetooth.enable &&
+            (lib.versionOlder config.boot.kernelPackages.kernel.version "5.12"))
           "options bluetooth disable_ertm=1";
 
       extraModulePackages = with config.boot.kernelPackages; [ xpadneo ];
diff --git a/nixos/modules/i18n/input-method/default.nix b/nixos/modules/i18n/input-method/default.nix
index bbc5783565a3..07fb86bcc25e 100644
--- a/nixos/modules/i18n/input-method/default.nix
+++ b/nixos/modules/i18n/input-method/default.nix
@@ -32,22 +32,20 @@ in
         type    = types.nullOr (types.enum [ "ibus" "fcitx" "fcitx5" "nabi" "uim" "hime" "kime" ]);
         default = null;
         example = "fcitx";
-        description = ''
+        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`.
+          - fcitx: A customizable lightweight input method, extra input engines can be added using `i18n.inputMethod.fcitx.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 +53,7 @@ in
         internal = true;
         type     = types.nullOr types.path;
         default  = null;
-        description = ''
+        description = lib.mdDoc ''
           The input method method package.
         '';
       };
diff --git a/nixos/modules/i18n/input-method/fcitx.nix b/nixos/modules/i18n/input-method/fcitx.nix
index 7738581b893a..043ec3d55c1f 100644
--- a/nixos/modules/i18n/input-method/fcitx.nix
+++ b/nixos/modules/i18n/input-method/fcitx.nix
@@ -22,9 +22,9 @@ in
           let
             enginesDrv = filterAttrs (const isDerivation) pkgs.fcitx-engines;
             engines = concatStringsSep ", "
-              (map (name: "<literal>${name}</literal>") (attrNames enginesDrv));
+              (map (name: "`${name}`") (attrNames enginesDrv));
           in
-            "Enabled Fcitx engines. Available engines are: ${engines}.";
+            lib.mdDoc "Enabled Fcitx engines. Available engines are: ${engines}.";
       };
     };
 
diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix
index 6fea28e22345..aa816c90a3de 100644
--- a/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixos/modules/i18n/input-method/fcitx5.nix
@@ -5,7 +5,9 @@ with lib;
 let
   im = config.i18n.inputMethod;
   cfg = im.fcitx5;
-  fcitx5Package = pkgs.fcitx5-with-addons.override { inherit (cfg) addons; };
+  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 {
   options = {
     i18n.inputMethod.fcitx5 = {
@@ -13,20 +15,34 @@ in {
         type = with types; listOf package;
         default = [];
         example = literalExpression "with pkgs; [ fcitx5-rime ]";
-        description = ''
+        description = lib.mdDoc ''
           Enabled Fcitx5 addons.
         '';
       };
+
+      enableRimeData = mkEnableOption (lib.mdDoc "default rime-data with fcitx5-rime");
     };
   };
 
   config = mkIf (im.enabled == "fcitx5") {
     i18n.inputMethod.package = fcitx5Package;
 
-    environment.variables = {
-      GTK_IM_MODULE = "fcitx";
-      QT_IM_MODULE = "fcitx";
-      XMODIFIERS = "@im=fcitx";
-    };
+    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";
+      };
+    })];
   };
 }
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index c5b0cbc21502..520db128acd9 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -23,6 +23,8 @@ let
       Name=IBus
       Type=Application
       Exec=${ibusPackage}/bin/ibus-daemon --daemonize --xim ${impanel}
+      # GNOME will launch ibus using systemd
+      NotShowIn=GNOME;
     '';
   };
 in
@@ -41,15 +43,15 @@ in
           let
             enginesDrv = filterAttrs (const isDerivation) pkgs.ibus-engines;
             engines = concatStringsSep ", "
-              (map (name: "<literal>${name}</literal>") (attrNames enginesDrv));
+              (map (name: "`${name}`") (attrNames enginesDrv));
           in
-            "Enabled IBus engines. Available engines are: ${engines}.";
+            lib.mdDoc "Enabled IBus engines. Available engines are: ${engines}.";
       };
       panel = mkOption {
         type = with types; nullOr path;
         default = null;
         example = literalExpression ''"''${pkgs.plasma5Packages.plasma-desktop}/lib/libexec/kimpanel-ibus-panel"'';
-        description = "Replace the IBus panel with another panel.";
+        description = lib.mdDoc "Replace the IBus panel with another panel.";
       };
     };
   };
@@ -67,7 +69,7 @@ in
     programs.dconf.packages = [ ibusPackage ];
 
     services.dbus.packages = [
-      ibusAutostart
+      ibusPackage
     ];
 
     environment.variables = {
diff --git a/nixos/modules/i18n/input-method/kime.nix b/nixos/modules/i18n/input-method/kime.nix
index 729a665614ae..29224a6bf75f 100644
--- a/nixos/modules/i18n/input-method/kime.nix
+++ b/nixos/modules/i18n/input-method/kime.nix
@@ -27,8 +27,8 @@ in
             };
           }
           '';
-        description = ''
-          kime configuration. Refer to <link xlink:href="https://github.com/Riey/kime/blob/v${pkgs.kime.version}/docs/CONFIGURATION.md"/> for details on supported values.
+        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.
         '';
       };
     };
diff --git a/nixos/modules/i18n/input-method/uim.nix b/nixos/modules/i18n/input-method/uim.nix
index 459294657e0a..9491ab2640fc 100644
--- a/nixos/modules/i18n/input-method/uim.nix
+++ b/nixos/modules/i18n/input-method/uim.nix
@@ -13,7 +13,7 @@ in
         type    = types.enum [ "gtk" "gtk3" "gtk-systray" "gtk3-systray" "qt4" ];
         default = "gtk";
         example = "gtk-systray";
-        description = ''
+        description = lib.mdDoc ''
           selected UIM toolbar.
         '';
       };
diff --git a/nixos/modules/installer/cd-dvd/channel.nix b/nixos/modules/installer/cd-dvd/channel.nix
index 92164d65e533..4b4c2e393348 100644
--- a/nixos/modules/installer/cd-dvd/channel.nix
+++ b/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 = nixpkgs;
+  };
+
   # Provide the NixOS/Nixpkgs sources in /etc/nixos.  This is required
   # for nixos-install.
   boot.postBootCommands = mkAfter
@@ -39,7 +53,8 @@ in
         echo "unpacking the NixOS/Nixpkgs sources..."
         mkdir -p /nix/var/nix/profiles/per-user/root
         ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/per-user/root/channels \
-          -i ${channelSources} --quiet --option build-use-substitutes false
+          -i ${channelSources} --quiet --option build-use-substitutes false \
+          ${optionalString config.boot.initrd.systemd.enable "--option sandbox false"} # There's an issue with pivot_root
         mkdir -m 0700 -p /root/.nix-defexpr
         ln -s /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels
         mkdir -m 0755 -p /var/lib/nixos
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-base.nix
index 618057618d0c..3f92b779d60a 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-base.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-base.nix
@@ -46,5 +46,5 @@ with lib;
     done
   '';
 
-  system.stateVersion = mkDefault "18.03";
+  system.stateVersion = lib.mkDefault lib.trivial.release;
 }
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
index fa19daf13280..4a00c52916f6 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
+++ b/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).
@@ -35,22 +35,35 @@ with lib;
   # Enable sound in graphical iso's.
   hardware.pulseaudio.enable = true;
 
-  environment.systemPackages = [
+  # VM guest additions to improve host-guest interaction
+  services.spice-vdagentd.enable = true;
+  services.qemuGuest.enable = true;
+  virtualisation.vmware.guest.enable = pkgs.stdenv.hostPlatform.isx86;
+  virtualisation.hypervGuest.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;
+
+  # Enable plymouth
+  boot.plymouth.enable = true;
+
+  environment.defaultPackages = with pkgs; [
     # Include gparted for partitioning disks.
-    pkgs.gparted
+    gparted
 
     # Include some editors.
-    pkgs.vim
-    pkgs.bvi # binary editor
-    pkgs.joe
+    vim
+    nano
 
     # Include some version control tools.
-    pkgs.git
+    git
+    rsync
 
     # Firefox for reading the manual.
-    pkgs.firefox
+    firefox
 
-    pkgs.glxinfo
+    glxinfo
   ];
 
 }
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix
new file mode 100644
index 000000000000..d015e10c11d8
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix
@@ -0,0 +1,60 @@
+# This module defines a NixOS installation CD that contains GNOME.
+
+{ pkgs, ... }:
+
+{
+  imports = [ ./installation-cd-graphical-calamares.nix ];
+
+  isoImage.edition = "gnome";
+
+  services.xserver.desktopManager.gnome = {
+    # Add Firefox and other tools useful for installation to the launcher
+    favoriteAppsOverride = ''
+      [org.gnome.shell]
+      favorite-apps=[ 'firefox.desktop', 'nixos-manual.desktop', 'org.gnome.Console.desktop', 'org.gnome.Nautilus.desktop', 'gparted.desktop', 'io.calamares.calamares.desktop' ]
+    '';
+
+    # Override GNOME defaults to disable GNOME tour and disable suspend
+    extraGSettingsOverrides = ''
+      [org.gnome.shell]
+      welcome-dialog-last-shown-version='9999999999'
+      [org.gnome.desktop.session]
+      idle-delay=0
+      [org.gnome.settings-daemon.plugins.power]
+      sleep-inactive-ac-type='nothing'
+      sleep-inactive-battery-type='nothing'
+    '';
+
+    extraGSettingsOverridePackages = [ pkgs.gnome.gnome-settings-daemon ];
+
+    enable = true;
+  };
+
+  # Theme calamares with GNOME theme
+  qt5 = {
+    enable = true;
+    platformTheme = "gnome";
+  };
+
+  # Fix scaling for calamares on wayland
+  environment.variables = {
+    QT_QPA_PLATFORM = "$([[ $XDG_SESSION_TYPE = \"wayland\" ]] && echo \"wayland\")";
+  };
+
+  services.xserver.displayManager = {
+    gdm = {
+      enable = true;
+      # autoSuspend makes the machine automatically suspend after inactivity.
+      # It's possible someone could/try to ssh'd into the machine and obviously
+      # have issues because it's inactive.
+      # See:
+      # * https://github.com/NixOS/nixpkgs/pull/63790
+      # * https://gitlab.gnome.org/GNOME/gnome-control-center/issues/22
+      autoSuspend = false;
+    };
+    autoLogin = {
+      enable = true;
+      user = "nixos";
+    };
+  };
+}
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma5.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma5.nix
new file mode 100644
index 000000000000..a4c46d58c85a
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma5.nix
@@ -0,0 +1,49 @@
+# This module defines a NixOS installation CD that contains X11 and
+# Plasma 5.
+
+{ pkgs, ... }:
+
+{
+  imports = [ ./installation-cd-graphical-calamares.nix ];
+
+  isoImage.edition = "plasma5";
+
+  services.xserver = {
+    desktopManager.plasma5 = {
+      enable = true;
+    };
+
+    # Automatically login as nixos.
+    displayManager = {
+      sddm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "nixos";
+      };
+    };
+  };
+
+  environment.systemPackages = with pkgs; [
+    # Graphical text editor
+    kate
+  ];
+
+  system.activationScripts.installerDesktop = let
+
+    # Comes from documentation.nix when xserver and nixos.enable are true.
+    manualDesktopFile = "/run/current-system/sw/share/applications/nixos-manual.desktop";
+
+    homeDir = "/home/nixos/";
+    desktopDir = homeDir + "Desktop/";
+
+  in ''
+    mkdir -p ${desktopDir}
+    chown nixos ${homeDir} ${desktopDir}
+
+    ln -sfT ${manualDesktopFile} ${desktopDir + "nixos-manual.desktop"}
+    ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop ${desktopDir + "gparted.desktop"}
+    ln -sfT ${pkgs.konsole}/share/applications/org.kde.konsole.desktop ${desktopDir + "org.kde.konsole.desktop"}
+    ln -sfT ${pkgs.calamares-nixos}/share/applications/io.calamares.calamares.desktop ${desktopDir + "io.calamares.calamares.desktop"}
+  '';
+
+}
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix
new file mode 100644
index 000000000000..3f3571d25382
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares.nix
@@ -0,0 +1,23 @@
+# This module adds the calamares installer to the basic graphical NixOS
+# installation CD.
+
+{ pkgs, ... }:
+let
+  calamares-nixos-autostart = pkgs.makeAutostartItem { name = "io.calamares.calamares"; package = pkgs.calamares-nixos; };
+in
+{
+  imports = [ ./installation-cd-graphical-base.nix ];
+
+  environment.systemPackages = with pkgs; [
+    # Calamares for graphical installation
+    libsForQt5.kpmcore
+    calamares-nixos
+    calamares-nixos-autostart
+    calamares-nixos-extensions
+    # Get list of locales
+    glibcLocales
+  ];
+
+  # Support choosing from any locale
+  i18n.supportedLocales = [ "all" ];
+}
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
index 303493741f3d..573b31b439c2 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
@@ -1,8 +1,6 @@
 # This module defines a NixOS installation CD that contains GNOME.
 
-{ lib, ... }:
-
-with lib;
+{ ... }:
 
 {
   imports = [ ./installation-cd-graphical-base.nix ];
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
index 098c2b2870b0..5c7617c9f8c1 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
@@ -1,9 +1,7 @@
 # This module defines a NixOS installation CD that contains X11 and
 # Plasma 5.
 
-{ config, lib, pkgs, ... }:
-
-with lib;
+{ pkgs, ... }:
 
 {
   imports = [ ./installation-cd-graphical-base.nix ];
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
index 97506045e0e1..abf0a5186b6a 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
@@ -1,14 +1,17 @@
 # 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";
+  documentation.man.enable = lib.mkOverride 500 true;
 
-  fonts.fontconfig.enable = false;
+  fonts.fontconfig.enable = lib.mkForce false;
+
+  isoImage.edition = lib.mkForce "minimal";
 }
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 860e240b43d4..81aca8617389 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -35,17 +35,21 @@ 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}";
@@ -53,6 +57,7 @@ let
       image = "/boot/${config.system.boot.loader.kernelFile}";
       initrd = "/boot/initrd";
     };
+
   in
     menuBuilderGrub2
     finalCfg
@@ -65,18 +70,16 @@ 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;
-
-
-  max = x: y: if x > y then x else y;
+      config.boot.loader.timeout * 10;
 
   # 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
@@ -314,16 +317,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 +334,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,39 +343,39 @@ 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"}
       }
     }
 
     ${lib.optionalString (refindBinary != null) ''
     # GRUB apparently cannot do "chainloader" operations on "CD".
     if [ "\$root" != "cd0" ]; then
-      # Force root to be the FAT partition
-      # Otherwise it breaks rEFInd's boot
-      search --set=root --no-floppy --fs-uuid 1234-5678
       menuentry 'rEFInd' --class refind {
+        # Force root to be the FAT partition
+        # Otherwise it breaks rEFInd's boot
+        search --set=root --no-floppy --fs-uuid 1234-5678
         chainloader (\$root)/EFI/boot/${refindBinary}
       }
     fi
@@ -400,10 +403,8 @@ let
     #   dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i)
     ''
       mkdir ./contents && cd ./contents
-      cp -rp "${efiDir}"/EFI .
-      mkdir ./boot
-      cp -p "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}" \
-        "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}" ./boot/
+      mkdir -p ./EFI/boot
+      cp -rp "${efiDir}"/EFI/boot/{grub.cfg,*.efi} ./EFI/boot
 
       # Rewrite dates for everything in the FS
       find . -exec touch --date=2000-01-01 {} +
@@ -418,14 +419,14 @@ 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 boot -type d | sort); do
+      for d in $(find EFI -type d | sort); do
         faketime "2000-01-01 00:00:00" mmd -i "$out" "::/$d"
       done
 
-      for f in $(find EFI boot -type f | sort); do
+      for f in $(find EFI -type f | sort); do
         mcopy -pvm -i "$out" "$f" "::/$f"
       done
 
@@ -433,19 +434,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;
 
@@ -456,34 +444,34 @@ in
 
     isoImage.isoName = mkOption {
       default = "${config.isoImage.isoBaseName}.iso";
-      description = ''
+      description = lib.mdDoc ''
         Name of the generated ISO image file.
       '';
     };
 
     isoImage.isoBaseName = mkOption {
       default = "nixos";
-      description = ''
+      description = lib.mdDoc ''
         Prefix of the name of the generated ISO image file.
       '';
     };
 
     isoImage.compressImage = mkOption {
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether the ISO image should be compressed using
-        <command>zstd</command>.
+        {command}`zstd`.
       '';
     };
 
     isoImage.squashfsCompression = mkOption {
       default = with pkgs.stdenv.targetPlatform; "xz -Xdict-size 100% "
-                + lib.optionalString (isx86_32 || isx86_64) "-Xbcj x86"
+                + lib.optionalString isx86 "-Xbcj x86"
                 # Untested but should also reduce size for these platforms
-                + lib.optionalString (isAarch32 || isAarch64) "-Xbcj arm"
-                + lib.optionalString (isPowerPC) "-Xbcj powerpc"
+                + lib.optionalString isAarch "-Xbcj arm"
+                + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc"
                 + lib.optionalString (isSparc) "-Xbcj sparc";
-      description = ''
+      description = lib.mdDoc ''
         Compression settings to use for the squashfs nix store.
       '';
       example = "zstd -Xcompression-level 6";
@@ -491,7 +479,7 @@ in
 
     isoImage.edition = mkOption {
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Specifies which edition string to use in the volume ID of the generated
         ISO image.
       '';
@@ -500,7 +488,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}";
-      description = ''
+      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
         mount the CD, so it should be reasonably distinctive.
@@ -514,7 +502,7 @@ in
           }
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option lists files to be copied to fixed locations in the
         generated ISO image.
       '';
@@ -522,7 +510,7 @@ in
 
     isoImage.storeContents = mkOption {
       example = literalExpression "[ pkgs.stdenv ]";
-      description = ''
+      description = lib.mdDoc ''
         This option lists additional derivations to be included in the
         Nix store in the generated ISO image.
       '';
@@ -530,7 +518,7 @@ in
 
     isoImage.includeSystemBuildDependencies = mkOption {
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Set this option to include all the needed sources etc in the
         image. It significantly increases image size. Use that when
         you want to be able to keep all the sources needed to build your
@@ -541,14 +529,14 @@ in
 
     isoImage.makeEfiBootable = mkOption {
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether the ISO image should be an efi-bootable volume.
       '';
     };
 
     isoImage.makeUsbBootable = mkOption {
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether the ISO image should be bootable from CD as well as USB.
       '';
     };
@@ -558,7 +546,7 @@ in
           url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/efi-background.png";
           sha256 = "18lfwmp8yq923322nlb9gxrh5qikj1wsk6g5qvdh31c4h5b1538x";
         };
-      description = ''
+      description = lib.mdDoc ''
         The splash image to use in the EFI bootloader.
       '';
     };
@@ -568,7 +556,7 @@ in
           url = "https://raw.githubusercontent.com/NixOS/nixos-artwork/a9e05d7deb38a8e005a2b52575a3f59a63a4dba0/bootloader/isolinux/bios-boot.png";
           sha256 = "1wp822zrhbg4fgfbwkr7cbkr4labx477209agzc0hr6k62fr6rxd";
         };
-      description = ''
+      description = lib.mdDoc ''
         The splash image to use in the legacy-boot bootloader.
       '';
     };
@@ -576,7 +564,7 @@ in
     isoImage.grubTheme = mkOption {
       default = pkgs.nixos-grub2-theme;
       type = types.nullOr (types.either types.path types.package);
-      description = ''
+      description = lib.mdDoc ''
         The grub2 theme used for UEFI boot.
       '';
     };
@@ -607,7 +595,7 @@ in
         MENU COLOR SEL          7;37;40    #FFFFFFFF    #FF5277C3   std
       '';
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The syslinux theme used for BIOS boot.
       '';
     };
@@ -615,12 +603,12 @@ in
     isoImage.appendToMenuLabel = mkOption {
       default = " Installer";
       example = " Live System";
-      description = ''
+      description = lib.mdDoc ''
         The string to append after the menu label for the NixOS system.
         This will be directly appended (without whitespace) to the NixOS version
-        string, like for example if it is set to <literal>XXX</literal>:
+        string, like for example if it is set to `XXX`:
 
-        <para><literal>NixOS 99.99-pre666XXX</literal></para>
+        `NixOS 99.99-pre666XXX`
       '';
     };
 
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix b/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
deleted file mode 100644
index 054c8c74a76b..000000000000
--- a/nixos/modules/installer/cd-dvd/system-tarball-fuloong2f.nix
+++ /dev/null
@@ -1,160 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  # A dummy /etc/nixos/configuration.nix in the booted CD that
-  # rebuilds the CD's configuration (and allows the configuration to
-  # be modified, of course, providing a true live CD).  Problem is
-  # that we don't really know how the CD was built - the Nix
-  # expression language doesn't allow us to query the expression being
-  # evaluated.  So we'll just hope for the best.
-  dummyConfiguration = pkgs.writeText "configuration.nix"
-    ''
-      { config, pkgs, ... }:
-
-      { # Add your own options below, e.g.:
-        #   services.openssh.enable = true;
-        nixpkgs.config.platform = pkgs.platforms.fuloong2f_n32;
-      }
-    '';
-
-
-  pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
-
-  # A clue for the kernel loading
-  kernelParams = pkgs.writeText "kernel-params.txt" ''
-    Kernel Parameters:
-      init=/boot/init ${toString config.boot.kernelParams}
-  '';
-
-  # System wide nixpkgs config
-  nixpkgsUserConfig = pkgs.writeText "config.nix" ''
-    pkgs:
-    {
-      platform = pkgs.platforms.fuloong2f_n32;
-    }
-  '';
-
-in
-
-{
-  imports = [ ./system-tarball.nix ];
-
-  # Disable some other stuff we don't need.
-  security.sudo.enable = false;
-
-  # Include only the en_US locale.  This saves 75 MiB or so compared to
-  # the full glibcLocales package.
-  i18n.supportedLocales = ["en_US.UTF-8/UTF-8" "en_US/ISO-8859-1"];
-
-  # Include some utilities that are useful for installing or repairing
-  # the system.
-  environment.systemPackages =
-    [ pkgs.w3m # needed for the manual anyway
-      pkgs.testdisk # useful for repairing boot problems
-      pkgs.ms-sys # for writing Microsoft boot sectors / MBRs
-      pkgs.parted
-      pkgs.ddrescue
-      pkgs.ccrypt
-      pkgs.cryptsetup # needed for dm-crypt volumes
-
-      # Some networking tools.
-      pkgs.sshfs-fuse
-      pkgs.socat
-      pkgs.screen
-      pkgs.wpa_supplicant # !!! should use the wpa module
-
-      # Hardware-related tools.
-      pkgs.sdparm
-      pkgs.hdparm
-      pkgs.dmraid
-
-      # Tools to create / manipulate filesystems.
-      pkgs.ntfsprogs # for resizing NTFS partitions
-      pkgs.btrfs-progs
-      pkgs.jfsutils
-
-      # Some compression/archiver tools.
-      pkgs.unzip
-      pkgs.zip
-      pkgs.xz
-      pkgs.dar # disk archiver
-
-      # Some editors.
-      pkgs.nvi
-      pkgs.bvi # binary editor
-      pkgs.joe
-    ];
-
-  # The initrd has to contain any module that might be necessary for
-  # mounting the CD/DVD.
-  boot.initrd.availableKernelModules =
-    [ "vfat" "reiserfs" ];
-
-  boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_10;
-  boot.kernelParams = [ "console=tty1" ];
-
-  boot.postBootCommands =
-    ''
-      mkdir -p /mnt
-
-      cp ${dummyConfiguration} /etc/nixos/configuration.nix
-    '';
-
-  # Some more help text.
-  services.getty.helpLine =
-    ''
-
-      Log in as "root" with an empty password.  ${
-        if config.services.xserver.enable then
-          "Type `start xserver' to start\nthe graphical user interface."
-        else ""
-      }
-    '';
-
-  # Include the firmware for various wireless cards.
-  networking.enableRalinkFirmware = true;
-  networking.enableIntel2200BGFirmware = true;
-
-  # To speed up further installation of packages, include the complete stdenv
-  # in the Nix store of the tarball.
-  tarball.storeContents = pkgs2storeContents [ pkgs.stdenv ]
-    ++ [
-      {
-        object = config.system.build.bootStage2;
-        symlink = "/boot/init";
-      }
-      {
-        object = config.system.build.toplevel;
-        symlink = "/boot/system";
-      }
-    ];
-
-  tarball.contents = [
-    { source = kernelParams;
-      target = "/kernelparams.txt";
-    }
-    { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
-      target = "/boot/" + config.system.boot.loader.kernelFile;
-    }
-    { source = nixpkgsUserConfig;
-      target = "/root/.nixpkgs/config.nix";
-    }
-  ];
-
-  # Allow sshd to be started manually through "start sshd".  It should
-  # not be started by default on the installation CD because the
-  # default root password is empty.
-  services.openssh.enable = true;
-  systemd.services.openssh.wantedBy = lib.mkOverride 50 [];
-
-  boot.loader.grub.enable = false;
-  boot.loader.generationsDir.enable = false;
-  system.boot.loader.kernelFile = "vmlinux";
-
-  nixpkgs.config = {
-    platform = pkgs.platforms.fuloong2f_n32;
-  };
-}
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt b/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt
deleted file mode 100644
index 887bf60d0fbe..000000000000
--- a/nixos/modules/installer/cd-dvd/system-tarball-pc-readme.txt
+++ /dev/null
@@ -1,89 +0,0 @@
-Let all the files in the system tarball sit in a directory served by NFS (the
-NFS root) like this in exportfs:
-  /home/pcroot    192.168.1.0/24(rw,no_root_squash,no_all_squash)
-
-Run "exportfs -a" after editing /etc/exportfs, for the nfs server to be aware
-of the changes.
-
-Use a tftp server serving the root of boot/ (from the system tarball).
-
-In order to have PXE boot, use the boot/dhcpd.conf-example file for your dhcpd
-server, as it will point your PXE clients to pxelinux.0 from the tftp server.
-Adapt the configuration to your network.
-
-Adapt the pxelinux configuration (boot/pxelinux.cfg/default) to set the path to
-your nfrroot. If you use ip=dhcp in the kernel, the nfs server ip will be taken
-from dhcp and so you don't have to specify it.
-
-The linux in bzImage includes network drivers for some usual cards.
-
-
-QEMU Testing
----------------
-
-You can test qemu pxe boot without having a DHCP server adapted, but having
-nfsroot, like this:
-  qemu-system-x86_64 -tftp /home/pcroot/boot -net nic -net user,bootfile=pxelinux.0 -boot n
-
-I don't know how to use NFS through the qemu '-net user' though.
-
-
-QEMU Testing with NFS root and bridged network
--------------------------------------------------
-
-This allows testing with qemu as any other host in your LAN.
-
-Testing with the real dhcpd server requires setting up a bridge and having a
-tap device.
-  tunctl -t tap0
-  brctl addbr br0
-  brctl addif br0 eth0
-  brctl addif tap0 eth0
-  ifconfig eth0 0.0.0.0 up
-  ifconfig tap0 0.0.0.0 up
-  ifconfig br0 up # With your ip configuration
-
-Then you can run qemu:
-  qemu-system-x86_64 -boot n -net tap,ifname=tap0,script=no -net nic,model=e1000
-
-
-Using the system-tarball-pc in a chroot
---------------------------------------------------
-
-Installation:
-  mkdir nixos-chroot && cd nixos-chroot
-  tar xf your-system-tarball.tar.xz
-  mkdir sys dev proc tmp root var run
-  mount --bind /sys sys
-  mount --bind /dev dev
-  mount --bind /proc proc
-
-Activate the system: look for a directory in nix/store similar to:
-    "/nix/store/y0d1lcj9fppli0hl3x0m0ba5g1ndjv2j-nixos-feb97bx-53f008"
-Having found it, activate that nixos system *twice*:
-  chroot . /nix/store/SOMETHING-nixos-SOMETHING/activate
-  chroot . /nix/store/SOMETHING-nixos-SOMETHING/activate
-
-This runs a 'hostname' command. Restore your old hostname with:
-  hostname OLDHOSTNAME
-
-Copy your system resolv.conf to the /etc/resolv.conf inside the chroot:
-  cp /etc/resolv.conf etc
-
-Then you can get an interactive shell in the nixos chroot. '*' means
-to run inside the chroot interactive shell
-  chroot . /bin/sh
-*  source /etc/profile
-
-Populate the nix database: that should be done in the init script if you
-had booted this nixos. Run:
-*  `grep local-cmds run/current-system/init`
-
-Then you can proceed normally subscribing to a nixos channel:
-  nix-channel --add https://nixos.org/channels/nixos-unstable
-  nix-channel --update
-
-Testing:
-  nix-env -i hello
-  which hello
-  hello
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
deleted file mode 100644
index 674fb6c8a33c..000000000000
--- a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
+++ /dev/null
@@ -1,163 +0,0 @@
-# This module contains the basic configuration for building a NixOS
-# tarball, that can directly boot, maybe using PXE or unpacking on a fs.
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
-
-  # For PXE kernel loading
-  pxeconfig = pkgs.writeText "pxeconfig-default" ''
-    default menu.c32
-    prompt 0
-
-    label bootlocal
-      menu default
-      localboot 0
-      timeout 80
-      TOTALTIMEOUT 9000
-
-    label nixos
-      MENU LABEL ^NixOS using nfsroot
-      KERNEL bzImage
-      append ip=dhcp nfsroot=/home/pcroot init=${config.system.build.toplevel}/init rw
-
-    # I don't know how to make this boot with nfsroot (using the initrd)
-    label nixos_initrd
-      MENU LABEL NixOS booting the poor ^initrd.
-      KERNEL bzImage
-      append initrd=initrd ip=dhcp nfsroot=/home/pcroot init=${config.system.build.toplevel}/init rw
-
-    label memtest
-      MENU LABEL ^${pkgs.memtest86.name}
-      KERNEL memtest
-  '';
-
-  dhcpdExampleConfig = pkgs.writeText "dhcpd.conf-example" ''
-    # Example configuration for booting PXE.
-    allow booting;
-    allow bootp;
-
-    # Adapt this to your network configuration.
-    option domain-name "local";
-    option subnet-mask 255.255.255.0;
-    option broadcast-address 192.168.1.255;
-    option domain-name-servers 192.168.1.1;
-    option routers 192.168.1.1;
-
-    # PXE-specific configuration directives...
-    # Some BIOS don't accept slashes for paths inside the tftp servers,
-    # and will report Access Violation if they see slashes.
-    filename "pxelinux.0";
-    # For the TFTP and NFS root server. Set the IP of your server.
-    next-server 192.168.1.34;
-
-    subnet 192.168.1.0 netmask 255.255.255.0 {
-      range 192.168.1.50 192.168.1.55;
-    }
-  '';
-
-  readme = ./system-tarball-pc-readme.txt;
-
-in
-
-{
-  imports =
-    [ ./system-tarball.nix
-
-      # Profiles of this basic installation.
-      ../../profiles/all-hardware.nix
-      ../../profiles/base.nix
-      ../../profiles/installation-device.nix
-    ];
-
-  # To speed up further installation of packages, include the complete stdenv
-  # in the Nix store of the tarball.
-  tarball.storeContents = pkgs2storeContents [ pkgs.stdenv ];
-
-  tarball.contents =
-    [ { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
-        target = "/boot/" + config.system.boot.loader.kernelFile;
-      }
-      { source = "${pkgs.syslinux}/share/syslinux/pxelinux.0";
-        target = "/boot/pxelinux.0";
-      }
-      { source = "${pkgs.syslinux}/share/syslinux/menu.c32";
-        target = "/boot/menu.c32";
-      }
-      { source = pxeconfig;
-        target = "/boot/pxelinux.cfg/default";
-      }
-      { source = readme;
-        target = "/readme.txt";
-      }
-      { source = dhcpdExampleConfig;
-        target = "/boot/dhcpd.conf-example";
-      }
-      { source = "${pkgs.memtest86}/memtest.bin";
-        # We can't leave '.bin', because pxelinux interprets this specially,
-        # and it would not load the image fine.
-        # http://forum.canardpc.com/threads/46464-0104-when-launched-via-pxe
-        target = "/boot/memtest";
-      }
-    ];
-
-  # Allow sshd to be started manually through "start sshd".  It should
-  # not be started by default on the installation CD because the
-  # default root password is empty.
-  services.openssh.enable = true;
-  systemd.services.openssh.wantedBy = lib.mkOverride 50 [];
-
-  # To be able to use the systemTarball to catch troubles.
-  boot.crashDump = {
-    enable = true;
-    kernelPackages = pkgs.linuxKernel.packages.linux_3_4;
-  };
-
-  # No grub for the tarball.
-  boot.loader.grub.enable = false;
-
-  /* fake entry, just to have a happy stage-1. Users
-     may boot without having stage-1 though */
-  fileSystems.fake =
-    { mountPoint = "/";
-      device = "/dev/something";
-    };
-
-  nixpkgs.config = {
-    packageOverrides = p: {
-      linux_3_4 = p.linux_3_4.override {
-        extraConfig = ''
-          # Enable drivers in kernel for most NICs.
-          E1000 y
-          # E1000E y
-          # ATH5K y
-          8139TOO y
-          NE2K_PCI y
-          ATL1 y
-          ATL1E y
-          ATL1C y
-          VORTEX y
-          VIA_RHINE y
-          R8169 y
-
-          # Enable nfs root boot
-          UNIX y # http://www.linux-mips.org/archives/linux-mips/2006-11/msg00113.html
-          IP_PNP y
-          IP_PNP_DHCP y
-          FSCACHE y
-          NFS_FS y
-          NFS_FSCACHE y
-          ROOT_NFS y
-
-          # Enable devtmpfs
-          DEVTMPFS y
-          DEVTMPFS_MOUNT y
-        '';
-      };
-    };
-  };
-}
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
deleted file mode 100644
index 329bd329dc15..000000000000
--- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
+++ /dev/null
@@ -1,172 +0,0 @@
-# This module contains the basic configuration for building a NixOS
-# tarball for the sheevaplug.
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  # A dummy /etc/nixos/configuration.nix in the booted CD that
-  # rebuilds the CD's configuration (and allows the configuration to
-  # be modified, of course, providing a true live CD).  Problem is
-  # that we don't really know how the CD was built - the Nix
-  # expression language doesn't allow us to query the expression being
-  # evaluated.  So we'll just hope for the best.
-  dummyConfiguration = pkgs.writeText "configuration.nix"
-    ''
-      { config, pkgs, ... }:
-
-      {
-        # Add your own options below and run "nixos-rebuild switch".
-        # E.g.,
-        #   services.openssh.enable = true;
-      }
-    '';
-
-
-  pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l;
-
-  # A clue for the kernel loading
-  kernelParams = pkgs.writeText "kernel-params.txt" ''
-    Kernel Parameters:
-      init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
-  '';
-
-
-in
-
-{
-  imports = [ ./system-tarball.nix ];
-
-  # Disable some other stuff we don't need.
-  security.sudo.enable = false;
-
-  # Include only the en_US locale.  This saves 75 MiB or so compared to
-  # the full glibcLocales package.
-  i18n.supportedLocales = ["en_US.UTF-8/UTF-8" "en_US/ISO-8859-1"];
-
-  # Include some utilities that are useful for installing or repairing
-  # the system.
-  environment.systemPackages =
-    [ pkgs.w3m # needed for the manual anyway
-      pkgs.ddrescue
-      pkgs.ccrypt
-      pkgs.cryptsetup # needed for dm-crypt volumes
-
-      # Some networking tools.
-      pkgs.sshfs-fuse
-      pkgs.socat
-      pkgs.screen
-      pkgs.wpa_supplicant # !!! should use the wpa module
-
-      # Hardware-related tools.
-      pkgs.sdparm
-      pkgs.hdparm
-      pkgs.dmraid
-
-      # Tools to create / manipulate filesystems.
-      pkgs.btrfs-progs
-
-      # Some compression/archiver tools.
-      pkgs.unzip
-      pkgs.zip
-      pkgs.xz
-      pkgs.dar # disk archiver
-
-      # Some editors.
-      pkgs.nvi
-      pkgs.bvi # binary editor
-      pkgs.joe
-    ];
-
-  boot.loader.grub.enable = false;
-  boot.loader.generationsDir.enable = false;
-  system.boot.loader.kernelFile = "uImage";
-
-  boot.initrd.availableKernelModules =
-    [ "mvsdio" "reiserfs" "ext3" "ums-cypress" "rtc_mv" "ext4" ];
-
-  boot.postBootCommands = lib.mkIf (!boot.initrd.systemd.enable)
-    ''
-      mkdir -p /mnt
-
-      cp ${dummyConfiguration} /etc/nixos/configuration.nix
-    '';
-
-  boot.initrd.extraUtilsCommands = lib.mkIf (!boot.initrd.systemd.enable)
-    ''
-      copy_bin_and_libs ${pkgs.util-linux}/sbin/hwclock
-    '';
-
-  boot.initrd.postDeviceCommands = lib.mkIf (!boot.initrd.systemd.enable)
-    ''
-      hwclock -s
-    '';
-
-  boot.kernelParams =
-    [
-      "selinux=0"
-      "console=tty1"
-      # "console=ttyS0,115200n8"  # serial console
-    ];
-
-  boot.kernelPackages = pkgs.linuxKernel.packages.linux_3_4;
-
-  boot.supportedFilesystems = [ "reiserfs" ];
-
-  /* fake entry, just to have a happy stage-1. Users
-     may boot without having stage-1 though */
-  fileSystems.fake =
-    { mountPoint = "/";
-      device = "/dev/something";
-    };
-
-  services.getty = {
-    # Some more help text.
-    helpLine = ''
-      Log in as "root" with an empty password.  ${
-        if config.services.xserver.enable then
-          "Type `start xserver' to start\nthe graphical user interface."
-        else ""
-      }
-    '';
-  };
-
-  # Setting vesa, we don't get the nvidia driver, which can't work in arm.
-  services.xserver.videoDrivers = [ "vesa" ];
-
-  documentation.nixos.enable = false;
-
-  # Include the firmware for various wireless cards.
-  networking.enableRalinkFirmware = true;
-  networking.enableIntel2200BGFirmware = true;
-
-  # To speed up further installation of packages, include the complete stdenv
-  # in the Nix store of the tarball.
-  tarball.storeContents = pkgs2storeContents [ pkgs.stdenv ];
-  tarball.contents = [
-    { source = kernelParams;
-      target = "/kernelparams.txt";
-    }
-    { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
-      target = "/boot/" + config.system.boot.loader.kernelFile;
-    }
-    { source = pkgs.ubootSheevaplug;
-      target = "/boot/uboot";
-    }
-  ];
-
-  # Allow sshd to be started manually through "start sshd".  It should
-  # not be started by default on the installation CD because the
-  # default root password is empty.
-  services.openssh.enable = true;
-  systemd.services.openssh.wantedBy = lib.mkOverride 50 [];
-
-  # cpufrequtils fails to build on non-pc
-  powerManagement.enable = false;
-
-  nixpkgs.config = {
-    platform = pkgs.platforms.sheevaplug;
-  };
-}
diff --git a/nixos/modules/installer/cd-dvd/system-tarball.nix b/nixos/modules/installer/cd-dvd/system-tarball.nix
deleted file mode 100644
index 362c555cc53e..000000000000
--- a/nixos/modules/installer/cd-dvd/system-tarball.nix
+++ /dev/null
@@ -1,93 +0,0 @@
-# This module creates a bootable ISO image containing the given NixOS
-# configuration.  The derivation for the ISO image will be placed in
-# config.system.build.tarball.
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  versionFile = pkgs.writeText "nixos-label" config.system.nixos.label;
-
-in
-
-{
-  options = {
-    tarball.contents = mkOption {
-      example = literalExpression ''
-        [ { source = pkgs.memtest86 + "/memtest.bin";
-            target = "boot/memtest.bin";
-          }
-        ]
-      '';
-      description = ''
-        This option lists files to be copied to fixed locations in the
-        generated ISO image.
-      '';
-    };
-
-    tarball.storeContents = mkOption {
-      example = literalExpression "[ pkgs.stdenv ]";
-      description = ''
-        This option lists additional derivations to be included in the
-        Nix store in the generated ISO image.
-      '';
-    };
-
-  };
-
-  config = {
-
-    # In stage 1 of the boot, mount the CD/DVD as the root FS by label
-    # so that we don't need to know its device.
-    fileSystems = { };
-
-    # boot.initrd.availableKernelModules = [ "mvsdio" "reiserfs" "ext3" "ext4" ];
-
-    # boot.initrd.kernelModules = [ "rtc_mv" ];
-
-    # Closures to be copied to the Nix store on the CD, namely the init
-    # script and the top-level system configuration directory.
-    tarball.storeContents =
-      [ { object = config.system.build.toplevel;
-          symlink = "/run/current-system";
-        }
-      ];
-
-    # Individual files to be included on the CD, outside of the Nix
-    # store on the CD.
-    tarball.contents =
-      [ { source = config.system.build.initialRamdisk + "/" + config.system.boot.loader.initrdFile;
-          target = "/boot/" + config.system.boot.loader.initrdFile;
-        }
-        { source = versionFile;
-          target = "/nixos-version.txt";
-        }
-      ];
-
-    # Create the tarball
-    system.build.tarball = import ../../../lib/make-system-tarball.nix {
-      inherit (pkgs) stdenv closureInfo pixz;
-
-      inherit (config.tarball) contents storeContents;
-    };
-
-    boot.postBootCommands =
-      ''
-        # After booting, register the contents of the Nix store on the
-        # CD in the Nix database in the tmpfs.
-        if [ -f /nix-path-registration ]; then
-          ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration &&
-          rm /nix-path-registration
-        fi
-
-        # nixos-rebuild also requires a "system" profile and an
-        # /etc/NIXOS tag.
-        touch /etc/NIXOS
-        ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
-      '';
-
-  };
-
-}
diff --git a/nixos/modules/installer/kexec/kexec-boot.nix b/nixos/modules/installer/kexec/kexec-boot.nix
deleted file mode 100644
index 2d062214efc2..000000000000
--- a/nixos/modules/installer/kexec/kexec-boot.nix
+++ /dev/null
@@ -1,51 +0,0 @@
-# This module exposes a config.system.build.kexecBoot attribute,
-# which returns a directory with kernel, initrd and a shell script
-# running the necessary kexec commands.
-
-# It's meant to be scp'ed to a machine with working ssh and kexec binary
-# installed.
-
-# This is useful for (cloud) providers where you can't boot a custom image, but
-# get some Debian or Ubuntu installation.
-
-{ pkgs
-, modulesPath
-, config
-, ...
-}:
-{
-  imports = [
-    (modulesPath + "/installer/netboot/netboot-minimal.nix")
-  ];
-
-  config = {
-    system.build.kexecBoot =
-      let
-        kexecScript = pkgs.writeScript "kexec-boot" ''
-          #!/usr/bin/env bash
-          if ! kexec -v >/dev/null 2>&1; then
-            echo "kexec not found: please install kexec-tools" 2>&1
-            exit 1
-          fi
-          SCRIPT_DIR=$( cd -- "$( dirname -- "''${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
-          kexec --load ''${SCRIPT_DIR}/bzImage \
-            --initrd=''${SCRIPT_DIR}/initrd.gz \
-            --command-line "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}"
-          kexec -e
-        ''; in
-      pkgs.linkFarm "kexec-tree" [
-        {
-          name = "initrd.gz";
-          path = "${config.system.build.netbootRamdisk}/initrd";
-        }
-        {
-          name = "bzImage";
-          path = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
-        }
-        {
-          name = "kexec-boot";
-          path = kexecScript;
-        }
-      ];
-  };
-}
diff --git a/nixos/modules/installer/netboot/netboot-minimal.nix b/nixos/modules/installer/netboot/netboot-minimal.nix
index 1563501a7e01..91065d52faf4 100644
--- a/nixos/modules/installer/netboot/netboot-minimal.nix
+++ b/nixos/modules/installer/netboot/netboot-minimal.nix
@@ -1,10 +1,12 @@
 # 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;
 }
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index a459e7304cd4..03bb529cd851 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -10,7 +10,7 @@ with lib;
 
     netboot.storeContents = mkOption {
       example = literalExpression "[ pkgs.stdenv ]";
-      description = ''
+      description = lib.mdDoc ''
         This option lists additional derivations to be included in the
         Nix store in the generated netboot image.
       '';
@@ -81,7 +81,7 @@ with lib;
 
 
     # Create the initrd
-    system.build.netbootRamdisk = pkgs.makeInitrd {
+    system.build.netbootRamdisk = pkgs.makeInitrdNG {
       inherit (config.boot.initrd) compressor;
       prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
 
@@ -101,6 +101,37 @@ with lib;
       boot
     '';
 
+    # A script invoking kexec on ./bzImage and ./initrd.gz.
+    # Usually used through system.build.kexecTree, but exposed here for composability.
+    system.build.kexecScript = pkgs.writeScript "kexec-boot" ''
+      #!/usr/bin/env bash
+      if ! kexec -v >/dev/null 2>&1; then
+        echo "kexec not found: please install kexec-tools" 2>&1
+        exit 1
+      fi
+      SCRIPT_DIR=$( cd -- "$( dirname -- "''${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+      kexec --load ''${SCRIPT_DIR}/bzImage \
+        --initrd=''${SCRIPT_DIR}/initrd.gz \
+        --command-line "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}"
+      kexec -e
+    '';
+
+    # A tree containing initrd.gz, bzImage and a kexec-boot script.
+    system.build.kexecTree = pkgs.linkFarm "kexec-tree" [
+      {
+        name = "initrd.gz";
+        path = "${config.system.build.netbootRamdisk}/initrd";
+      }
+      {
+        name = "bzImage";
+        path = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
+      }
+      {
+        name = "kexec-boot";
+        path = config.system.build.kexecScript;
+      }
+    ];
+
     boot.loader.timeout = 10;
 
     boot.postBootCommands =
diff --git a/nixos/modules/installer/sd-card/sd-image.nix b/nixos/modules/installer/sd-card/sd-image.nix
index 7560c682517a..ad9b803b1d1e 100644
--- a/nixos/modules/installer/sd-card/sd-image.nix
+++ b/nixos/modules/installer/sd-card/sd-image.nix
@@ -18,7 +18,7 @@ with lib;
 let
   rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({
     inherit (config.sdImage) storePaths;
-    compressImage = true;
+    compressImage = config.sdImage.compressImage;
     populateImageCommands = config.sdImage.populateRootCommands;
     volumeLabel = "NIXOS_SD";
   } // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
@@ -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.
       '';
     };
@@ -174,7 +174,8 @@ in
     mtools, libfaketime, util-linux, zstd }: stdenv.mkDerivation {
       name = config.sdImage.imageName;
 
-      nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime util-linux zstd ];
+      nativeBuildInputs = [ dosfstools e2fsprogs libfaketime mtools util-linux ]
+      ++ lib.optional config.sdImage.compressImage zstd;
 
       inherit (config.sdImage) imageName compressImage;
 
@@ -189,14 +190,18 @@ in
           echo "file sd-image $img" >> $out/nix-support/hydra-build-products
         fi
 
+        root_fs=${rootfsImage}
+        ${lib.optionalString config.sdImage.compressImage ''
+        root_fs=./root-fs.img
         echo "Decompressing rootfs image"
-        zstd -d --no-progress "${rootfsImage}" -o ./root-fs.img
+        zstd -d --no-progress "${rootfsImage}" -o $root_fs
+        ''}
 
         # Gap in front of the first partition, in MiB
         gap=${toString config.sdImage.firmwarePartitionOffset}
 
         # Create the image file sized to fit /boot/firmware and /, plus slack for the gap.
-        rootSizeBlocks=$(du -B 512 --apparent-size ./root-fs.img | awk '{ print $1 }')
+        rootSizeBlocks=$(du -B 512 --apparent-size $root_fs | awk '{ print $1 }')
         firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
         imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
         truncate -s $imageSize $img
@@ -214,19 +219,30 @@ in
 
         # Copy the rootfs into the SD image
         eval $(partx $img -o START,SECTORS --nr 2 --pairs)
-        dd conv=notrunc if=./root-fs.img of=$img seek=$START count=$SECTORS
+        dd conv=notrunc if=$root_fs of=$img seek=$START count=$SECTORS
 
         # 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/nixos/modules/installer/tools/get-version-suffix b/nixos/modules/installer/tools/get-version-suffix
index b8972cd57d22..8d72905cdcb4 100644
--- a/nixos/modules/installer/tools/get-version-suffix
+++ b/nixos/modules/installer/tools/get-version-suffix
@@ -1,14 +1,15 @@
 getVersion() {
     local dir="$1"
     rev=
-    if [ -e "$dir/.git" ]; then
+    gitDir="$dir/.git"
+    if [ -e "$gitDir" ]; then
         if [ -z "$(type -P git)" ]; then
             echo "warning: Git not found; cannot figure out revision of $dir" >&2
             return
         fi
         cd "$dir"
-        rev=$(git rev-parse --short HEAD)
-        if git describe --always --dirty | grep -q dirty; then
+        rev=$(git --git-dir="$gitDir" rev-parse --short HEAD)
+        if git --git-dir="$gitDir" describe --always --dirty | grep -q dirty; then
             rev+=M
         fi
     fi
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 1707935ad5bc..3eca901bdbf7 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/yx36yzxpw1hn4fz8iyf1rfyd56jg3yf4-nix-2.8.0";
-  i686-linux = "/nix/store/c0hg806zvwg800qbszzj8ff4a224kjgf-nix-2.8.0";
-  aarch64-linux = "/nix/store/wic2832ll53q392r2wks4xr2nrk7p8p5-nix-2.8.0";
-  x86_64-darwin = "/nix/store/5yqdvnkmkrhl36xh0qy31pymdphjimdd-nix-2.8.0";
-  aarch64-darwin = "/nix/store/izc9592szrnpv8n86hr88bhpyc9g6b4s-nix-2.8.0";
+  x86_64-linux = "/nix/store/h88w1442c7hzkbw8sgpcsbqp4lhz6l5p-nix-2.12.0";
+  i686-linux = "/nix/store/j23527l1c3hfx17nssc0v53sq6c741zs-nix-2.12.0";
+  aarch64-linux = "/nix/store/zgzmdymyh934y3r4vqh8z337ba4cwsjb-nix-2.12.0";
+  x86_64-darwin = "/nix/store/wnlrzllazdyg1nrw9na497p4w0m7i7mm-nix-2.12.0";
+  aarch64-darwin = "/nix/store/7n5yamgzg5dpp5vb6ipdqgfh6cf30wmn-nix-2.12.0";
 }
diff --git a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
index b4a94f62ad93..21a257378a63 100644
--- a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
+++ b/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/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index fb5d3ba47325..212b2b3cd23a 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -84,6 +84,15 @@ 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;";
+
+
 my $cpuinfo = read_file "/proc/cpuinfo";
 
 
@@ -291,6 +300,12 @@ if ($virt eq "oracle") {
     push @attrs, "virtualisation.virtualbox.guest.enable = true;"
 }
 
+# Check if we're a Parallels guest. If so, enable the guest additions.
+# It is blocked by https://github.com/systemd/systemd/pull/23859
+if ($virt eq "parallels") {
+    push @attrs, "hardware.parallels.enable = true;";
+    push @attrs, "nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ \"prl-tools\" ];";
+}
 
 # Likewise for QEMU.
 if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
@@ -581,17 +596,19 @@ ${\join "", (map { "  $_\n" } (uniq @attrs))}}
 EOF
 
 sub generateNetworkingDhcpConfig {
+    # FIXME disable networking.useDHCP by default when switching to networkd.
     my $config = <<EOF;
-  # The global useDHCP flag is deprecated, therefore explicitly set to false here.
-  # Per-interface useDHCP will be mandatory in the future, so this generated config
-  # replicates the default behaviour.
-  networking.useDHCP = lib.mkDefault false;
+  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
+  # (the default) this is the recommended approach. When using systemd-networkd it's
+  # still possible to use this option, but it's recommended to use it in conjunction
+  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
+  networking.useDHCP = lib.mkDefault true;
 EOF
 
     foreach my $path (glob "/sys/class/net/*") {
         my $dev = basename($path);
         if ($dev ne "lo") {
-            $config .= "  networking.interfaces.$dev.useDHCP = lib.mkDefault true;\n";
+            $config .= "  # networking.interfaces.$dev.useDHCP = lib.mkDefault true;\n";
         }
     }
 
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index bf5ec0f9690b..e46a2df8fa6a 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -34,7 +34,8 @@ let
     name = "nixos-generate-config";
     src = ./nixos-generate-config.pl;
     perl = "${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl";
-    detectvirt = "${pkgs.systemd}/bin/systemd-detect-virt";
+    nixInstantiate = "${pkgs.nix}/bin/nix-instantiate";
+    detectvirt = "${config.systemd.package}/bin/systemd-detect-virt";
     btrfs = "${pkgs.btrfs-progs}/bin/btrfs";
     inherit (config.system.nixos-generate-config) configuration desktopConfiguration;
     xserverEnabled = config.services.xserver.enable;
@@ -74,15 +75,15 @@ in
     configuration = mkOption {
       internal = true;
       type = types.str;
-      description = ''
-        The NixOS module that <literal>nixos-generate-config</literal>
-        saves to <literal>/etc/nixos/configuration.nix</literal>.
+      description = lib.mdDoc ''
+        The NixOS module that `nixos-generate-config`
+        saves to `/etc/nixos/configuration.nix`.
 
         This is an internal option. No backward compatibility is guaranteed.
         Use at your own risk!
 
         Note that this string gets spliced into a Perl script. The perl
-        variable <literal>$bootLoaderConfig</literal> can be used to
+        variable `$bootLoaderConfig` can be used to
         splice in the boot loader configuration.
       '';
     };
@@ -91,15 +92,15 @@ in
       internal = true;
       type = types.listOf types.lines;
       default = [];
-      description = ''
-        Text to preseed the desktop configuration that <literal>nixos-generate-config</literal>
-        saves to <literal>/etc/nixos/configuration.nix</literal>.
+      description = lib.mdDoc ''
+        Text to preseed the desktop configuration that `nixos-generate-config`
+        saves to `/etc/nixos/configuration.nix`.
 
         This is an internal option. No backward compatibility is guaranteed.
         Use at your own risk!
 
         Note that this string gets spliced into a Perl script. The perl
-        variable <literal>$bootLoaderConfig</literal> can be used to
+        variable `$bootLoaderConfig` can be used to
         splice in the boot loader configuration.
       '';
     };
@@ -109,7 +110,7 @@ in
     internal = true;
     type = types.bool;
     default = false;
-    description = ''
+    description = lib.mdDoc ''
       Disable nixos-rebuild, nixos-generate-config, nixos-installer
       and other NixOS tools. This is useful to shrink embedded,
       read-only systems which are not expected to be rebuild or
@@ -174,9 +175,13 @@ in
         # services.xserver.libinput.enable = true;
 
         # Define a user account. Don't forget to set a password with ‘passwd’.
-        # users.users.jane = {
+        # users.users.alice = {
         #   isNormalUser = true;
         #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
+        #   packages = with pkgs; [
+        #     firefox
+        #     thunderbird
+        #   ];
         # };
 
         # List packages installed in system profile. To search, run:
@@ -184,7 +189,6 @@ in
         # environment.systemPackages = with pkgs; [
         #   vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
         #   wget
-        #   firefox
         # ];
 
         # Some programs need SUID wrappers, can be configured further or are
diff --git a/nixos/modules/misc/assertions.nix b/nixos/modules/misc/assertions.nix
index 550b3ac97f6a..364bb02be82d 100644
--- a/nixos/modules/misc/assertions.nix
+++ b/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/nixos/modules/misc/crashdump.nix b/nixos/modules/misc/crashdump.nix
index b0f75d9caaa3..4ae18984ee5f 100644
--- a/nixos/modules/misc/crashdump.nix
+++ b/nixos/modules/misc/crashdump.nix
@@ -16,7 +16,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             If enabled, NixOS will set up a kernel that will
             boot on crash, and leave the user in systemd rescue
             to be able to save the crashed kernel dump at
@@ -27,7 +27,7 @@ in
         reservedMemory = mkOption {
           default = "128M";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The amount of memory reserved for the crashdump kernel.
             If you choose a too high value, dmesg will mention
             "crashkernel reservation failed".
@@ -36,7 +36,7 @@ in
         kernelParams = mkOption {
           type = types.listOf types.str;
           default = [ "1" "boot.shell_on_fail" ];
-          description = ''
+          description = lib.mdDoc ''
             Parameters that will be passed to the kernel kexec-ed on crash.
           '';
         };
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 8e28d3336fa4..64a8f7846b46 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -1,4 +1,4 @@
-{ config, options, lib, pkgs, utils, modules, baseModules, extraModules, modulesPath, ... }:
+{ config, options, lib, pkgs, utils, modules, baseModules, extraModules, modulesPath, specialArgs, ... }:
 
 with lib;
 
@@ -7,9 +7,6 @@ let
   cfg = config.documentation;
   allOpts = options;
 
-  /* Modules for which to show options even when not imported. */
-  extraDocModules = [ ../virtualisation/qemu-vm.nix ];
-
   canCacheDocs = m:
     let
       f = import m;
@@ -23,7 +20,7 @@ let
 
   docModules =
     let
-      p = partition canCacheDocs (baseModules ++ extraDocModules);
+      p = partition canCacheDocs (baseModules ++ cfg.nixos.extraModules);
     in
       {
         lazy = p.right;
@@ -41,7 +38,7 @@ let
           modules = [ {
             _module.check = false;
           } ] ++ docModules.eager;
-          specialArgs = {
+          specialArgs = specialArgs // {
             pkgs = scrubDerivations "pkgs" pkgs;
             # allow access to arbitrary options for eager modules, eg for getting
             # option types from lazy modules
@@ -51,14 +48,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` 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 =
@@ -70,9 +73,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
@@ -102,7 +105,8 @@ let
               exit 1
             } >&2
         '';
-    inherit (cfg.nixos.options) warningsAreErrors;
+
+    inherit (cfg.nixos.options) warningsAreErrors allowDocBook;
   };
 
 
@@ -145,6 +149,12 @@ in
 
 {
   imports = [
+    ./man-db.nix
+    ./mandoc.nix
+    ./assertions.nix
+    ./meta.nix
+    ../config/system-path.nix
+    ../system/etc/etc.nix
     (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
     (mkRenamedOptionModule [ "programs" "man"  "enable" ] [ "documentation" "man"  "enable" ])
     (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
@@ -157,9 +167,9 @@ in
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install documentation of packages from
-          <option>environment.systemPackages</option> into the generated system path.
+          {option}`environment.systemPackages` into the generated system path.
 
           See "Multiple-output packages" chapter in the nixpkgs manual for more info.
         '';
@@ -169,36 +179,29 @@ in
       man.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install manual pages.
-          This also includes <literal>man</literal> outputs.
+          This also includes `man` outputs.
         '';
       };
 
       man.generateCaches = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = mdDoc ''
           Whether to generate the manual page index caches.
           This allows searching for a page or
-          keyword using utilities like
-          <citerefentry>
-            <refentrytitle>apropos</refentrytitle>
-            <manvolnum>1</manvolnum>
-          </citerefentry>
-          and the <literal>-k</literal> option of
-          <citerefentry>
-            <refentrytitle>man</refentrytitle>
-            <manvolnum>1</manvolnum>
-          </citerefentry>.
+          keyword using utilities like {manpage}`apropos(1)`
+          and the `-k` option of
+          {manpage}`man(1)`.
         '';
       };
 
       info.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          Whether to install info pages and the <command>info</command> command.
+        description = lib.mdDoc ''
+          Whether to install info pages and the {command}`info` command.
           This also includes "info" outputs.
         '';
       };
@@ -206,8 +209,8 @@ in
       doc.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          Whether to install documentation distributed in packages' <literal>/share/doc</literal>.
+        description = lib.mdDoc ''
+          Whether to install documentation distributed in packages' `/share/doc`.
           Usually plain text and/or HTML.
           This also includes "doc" outputs.
         '';
@@ -216,49 +219,70 @@ in
       dev.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = mdDoc ''
           Whether to install documentation targeted at developers.
-          <itemizedlist>
-          <listitem><para>This includes man pages targeted at developers if <option>documentation.man.enable</option> is
-                    set (this also includes "devman" outputs).</para></listitem>
-          <listitem><para>This includes info pages targeted at developers if <option>documentation.info.enable</option>
-                    is set (this also includes "devinfo" outputs).</para></listitem>
-          <listitem><para>This includes other pages targeted at developers if <option>documentation.doc.enable</option>
-                    is set (this also includes "devdoc" outputs).</para></listitem>
-          </itemizedlist>
+          * This includes man pages targeted at developers if {option}`documentation.man.enable` is
+            set (this also includes "devman" outputs).
+          * This includes info pages targeted at developers if {option}`documentation.info.enable`
+            is set (this also includes "devinfo" outputs).
+          * This includes other pages targeted at developers if {option}`documentation.doc.enable`
+            is set (this also includes "devdoc" outputs).
         '';
       };
 
       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 = lib.mdDoc ''
+          Modules for which to show options even when not imported.
         '';
       };
 
       nixos.options.splitBuild = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to split the option docs build into a cacheable and an uncacheable part.
           Splitting the build can substantially decrease the amount of time needed to build
           the manual, but some user modules may be incompatible with this splitting.
         '';
       };
 
+      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;
-        description = ''
+        description = lib.mdDoc ''
           Treat warning emitted during the option documentation build (eg for missing option
           descriptions) as errors.
         '';
@@ -267,18 +291,18 @@ in
       nixos.includeAllModules = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether the generated NixOS's documentation should include documentation for all
           the options from all the NixOS modules included in the current
-          <literal>configuration.nix</literal>. Disabling this will make the manual
-          generator to ignore options defined outside of <literal>baseModules</literal>.
+          `configuration.nix`. Disabling this will make the manual
+          generator to ignore options defined outside of `baseModules`.
         '';
       };
 
       nixos.extraModuleSources = mkOption {
         type = types.listOf (types.either types.path types.str);
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           Which extra NixOS module paths the generated NixOS's documentation should strip
           from options.
         '';
@@ -336,10 +360,6 @@ in
       environment.systemPackages = []
         ++ optional cfg.man.enable manual.manpages
         ++ optionals cfg.doc.enable [ manual.manualHTML nixos-help ];
-
-      services.getty.helpLine = mkIf cfg.doc.enable (
-          "\nRun 'nixos-help' for the NixOS manual."
-      );
     })
 
   ]);
diff --git a/nixos/modules/misc/documentation/test-dummy.chapter.xml b/nixos/modules/misc/documentation/test-dummy.chapter.xml
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/nixos/modules/misc/documentation/test-dummy.chapter.xml
diff --git a/nixos/modules/misc/documentation/test.nix b/nixos/modules/misc/documentation/test.nix
new file mode 100644
index 000000000000..dd1588abdb43
--- /dev/null
+++ b/nixos/modules/misc/documentation/test.nix
@@ -0,0 +1,49 @@
+{ nixosLib, pkgsModule, runCommand }:
+
+let
+  sys = nixosLib.evalModules rec {
+    modules = [
+      pkgsModule
+      ../documentation.nix
+      ../version.nix
+
+      ({ lib, someArg, ... }: {
+        # Make sure imports from specialArgs are respected
+        imports = [ someArg.myModule ];
+
+        # TODO test this
+        meta.doc = ./test-dummy.chapter.xml;
+      })
+
+      {
+        _module.args = {
+          baseModules = [
+            ../documentation.nix
+            ../version.nix
+          ];
+          extraModules = [ ];
+          inherit modules;
+        };
+        documentation.nixos.includeAllModules = true;
+      }
+    ];
+    specialArgs.someArg.myModule = { lib, ... }: {
+      options.foobar = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc "The foobar option was added via specialArgs";
+        default = "qux";
+      };
+    };
+  };
+
+in
+runCommand "documentation-check"
+{
+  inherit (sys.config.system.build.manual) optionsJSON;
+} ''
+  json="$optionsJSON/share/doc/nixos/options.json"
+  echo checking $json
+
+  grep 'The foobar option was added via specialArgs' <"$json" >/dev/null
+  touch $out
+''
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 7d1faa50f4bf..17ea04cb4ecb 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -3,7 +3,7 @@
 
 # IMPORTANT!
 # We only add static uids and gids for services where it is not feasible
-# to change uids/gids on service start, in example a service with a lot of
+# to change uids/gids on service start, for example a service with a lot of
 # files. Please also check if the service is applicable for systemd's
 # DynamicUser option and does not need a uid/gid allocation at all.
 # Systemd can also change ownership of service directories using the
@@ -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;
@@ -236,7 +236,7 @@ in
       gitit = 202;
       riemanntools = 203;
       subsonic = 204;
-      riak = 205;
+      # riak = 205; # unused, remove 2022-07-22
       #shout = 206; # dynamically allocated as of 2021-09-18
       gateone = 207;
       namecoin = 208;
@@ -354,6 +354,8 @@ in
       webdav = 322;
       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!
 
@@ -553,7 +555,7 @@ in
       gitit = 202;
       riemanntools = 203;
       subsonic = 204;
-      riak = 205;
+      # riak = 205;#unused, removed 2022-06-22
       #shout = 206; #unused
       gateone = 207;
       namecoin = 208;
@@ -662,11 +664,34 @@ in
       webdav = 322;
       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
       # uids and gids. Also, don't use gids above 399!
 
+      # For exceptional cases where you really need a gid above 399, leave a
+      # comment stating why.
+      #
+      # Also, avoid the following GID ranges:
+      #
+      #  1000 - 29999: user accounts (see ../config/update-users-groups.pl)
+      # 30000 - 31000: nixbld users (the upper limit is arbitrarily chosen)
+      # 61184 - 65519: systemd DynamicUser (see systemd.exec(5))
+      #         65535: the error return sentinel value when uid_t was 16 bits
+      #
+      # 100000 - 6653600: subgid allocated for user namespaces
+      #                   (see ../config/update-users-groups.pl)
+      #       4294967294: unauthenticated user in some NFS implementations
+      #       4294967295: error return sentinel value
+      #
+      # References:
+      # https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes
+
+      onepassword = 31001; # 1Password requires that its GID be larger than 1000
+      onepassword-cli = 31002; # 1Password requires that its GID be larger than 1000
+
       users = 100;
       nixbld = 30000;
       nogroup = 65534;
diff --git a/nixos/modules/misc/label.nix b/nixos/modules/misc/label.nix
index 02b91555b3c2..44ee812249ce 100644
--- a/nixos/modules/misc/label.nix
+++ b/nixos/modules/misc/label.nix
@@ -11,31 +11,35 @@ in
   options.system = {
 
     nixos.label = mkOption {
-      type = types.str;
-      description = ''
+      type = types.strMatching "[a-zA-Z0-9:_\\.-]*";
+      description = lib.mdDoc ''
         NixOS version name to be used in the names of generated
         outputs and boot labels.
 
         If you ever wanted to influence the labels in your GRUB menu,
         this is the option for you.
 
-        The default is <option>system.nixos.tags</option> separated by
-        "-" + "-" + <envar>NIXOS_LABEL_VERSION</envar> environment
+        It can only contain letters, numbers and the following symbols:
+        `:`, `_`, `.` and `-`.
+
+        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
+        ```
       '';
     };
 
@@ -43,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/nixos/modules/misc/lib.nix b/nixos/modules/misc/lib.nix
index 121f396701ea..f97e9209e2f1 100644
--- a/nixos/modules/misc/lib.nix
+++ b/nixos/modules/misc/lib.nix
@@ -7,7 +7,7 @@
 
       type = lib.types.attrsOf lib.types.attrs;
 
-      description = ''
+      description = lib.mdDoc ''
         This option allows modules to define helper functions, constants, etc.
       '';
     };
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
index 192c9ec413cb..acf441cda628 100644
--- a/nixos/modules/misc/locate.nix
+++ b/nixos/modules/misc/locate.nix
@@ -19,9 +19,9 @@ in
     enable = mkOption {
       type = bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If enabled, NixOS will periodically update the database of
-        files used by the <command>locate</command> command.
+        files used by the {command}`locate` command.
       '';
     };
 
@@ -30,7 +30,7 @@ in
       default = pkgs.findutils.locate;
       defaultText = literalExpression "pkgs.findutils";
       example = literalExpression "pkgs.mlocate";
-      description = ''
+      description = lib.mdDoc ''
         The locate implementation to use
       '';
     };
@@ -39,31 +39,30 @@ in
       type = str;
       default = "02:15";
       example = "hourly";
-      description = ''
+      description = lib.mdDoc ''
         Update the locate database at this interval. Updates by
         default at 2:15 AM every day.
 
         The format is described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
+        {manpage}`systemd.time(7)`.
 
-        To disable automatic updates, set to <literal>"never"</literal>
-        and run <command>updatedb</command> manually.
+        To disable automatic updates, set to `"never"`
+        and run {command}`updatedb` manually.
       '';
     };
 
     extraFlags = mkOption {
       type = listOf str;
       default = [ ];
-      description = ''
-        Extra flags to pass to <command>updatedb</command>.
+      description = lib.mdDoc ''
+        Extra flags to pass to {command}`updatedb`.
       '';
     };
 
     output = mkOption {
       type = path;
       default = "/var/cache/locatedb";
-      description = ''
+      description = lib.mdDoc ''
         The database file to build.
       '';
     };
@@ -71,9 +70,9 @@ in
     localuser = mkOption {
       type = nullOr str;
       default = "nobody";
-      description = ''
+      description = lib.mdDoc ''
         The user to search non-network directories as, using
-        <command>su</command>.
+        {command}`su`.
       '';
     };
 
@@ -159,7 +158,7 @@ in
         "vboxsf"
         "vperfctrfs"
       ];
-      description = ''
+      description = lib.mdDoc ''
         Which filesystem types to exclude from indexing
       '';
     };
@@ -176,7 +175,7 @@ in
         "/nix/store"
         "/nix/var/log/nix"
       ];
-      description = ''
+      description = lib.mdDoc ''
         Which paths to exclude from indexing
       '';
     };
@@ -184,11 +183,11 @@ 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 = ''
+      description = lib.mdDoc ''
         Directory components which should exclude paths containing them from indexing
       '';
     };
@@ -196,7 +195,7 @@ in
     pruneBindMounts = mkOption {
       type = bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether not to index bind mounts
       '';
     };
@@ -250,7 +249,7 @@ in
     };
 
     warnings = optional (isMorPLocate && cfg.localuser != null)
-      "mlocate does not support the services.locate.localuser option; updatedb will run as root. (Silence with services.locate.localuser = null.)"
+      "mlocate and plocate do not support the services.locate.localuser option. updatedb will run as root. Silence this warning by setting services.locate.localuser = null."
     ++ optional (isFindutils && cfg.pruneNames != [ ])
       "findutils locate does not support pruning by directory component"
     ++ optional (isFindutils && cfg.pruneBindMounts)
diff --git a/nixos/modules/misc/man-db.nix b/nixos/modules/misc/man-db.nix
index 8bd329bc4e0c..299b11d1fcef 100644
--- a/nixos/modules/misc/man-db.nix
+++ b/nixos/modules/misc/man-db.nix
@@ -7,7 +7,7 @@ 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;
@@ -23,11 +23,11 @@ in
             ++ lib.optionals config.documentation.dev.enable [ "devman" ];
           ignoreCollisions = true;
         };
-        defaultText = lib.literalDocBook "all man pages in <option>config.environment.systemPackages</option>";
-        description = ''
-          The manual pages to generate caches for if <option>documentation.man.generateCaches</option>
+        defaultText = lib.literalMD "all man pages in {option}`config.environment.systemPackages`";
+        description = lib.mdDoc ''
+          The manual pages to generate caches for if {option}`documentation.man.generateCaches`
           is enabled. Must be a path to a directory with man pages under
-          <literal>/share/man</literal>; see the source for an example.
+          `/share/man`; see the source for an example.
           Advanced users can make this a content-addressed derivation to save a few rebuilds.
         '';
       };
@@ -36,8 +36,8 @@ in
         type = lib.types.package;
         default = pkgs.man-db;
         defaultText = lib.literalExpression "pkgs.man-db";
-        description = ''
-          The <literal>man-db</literal> derivation to use. Useful to override
+        description = lib.mdDoc ''
+          The `man-db` derivation to use. Useful to override
           configuration options used for the package.
         '';
       };
@@ -52,9 +52,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/nixos/modules/misc/mandoc.nix b/nixos/modules/misc/mandoc.nix
index 3da60f2f8e65..9bcef5b1a09b 100644
--- a/nixos/modules/misc/mandoc.nix
+++ b/nixos/modules/misc/mandoc.nix
@@ -10,15 +10,15 @@ 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;
         default = [ "share/man" ];
         example = lib.literalExpression "[ \"share/man\" \"share/man/fr\" ]";
-        description = ''
+        description = lib.mdDoc ''
           Change the manpath, i. e. the directories where
-          <citerefentry><refentrytitle>man</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+          {manpage}`man(1)`
           looks for section-specific directories of man pages.
           You only need to change this setting if you want extra man pages
           (e. g. in non-english languages). All values must be strings that
@@ -31,8 +31,8 @@ in {
         type = lib.types.package;
         default = pkgs.mandoc;
         defaultText = lib.literalExpression "pkgs.mandoc";
-        description = ''
-          The <literal>mandoc</literal> derivation to use. Useful to override
+        description = lib.mdDoc ''
+          The `mandoc` derivation to use. Useful to override
           configuration options used for the package.
         '';
       };
@@ -53,7 +53,9 @@ in {
       # see: https://inbox.vuxu.org/mandoc-tech/20210906171231.GF83680@athene.usta.de/T/#e85f773c1781e3fef85562b2794f9cad7b2909a3c
       extraSetup = lib.mkIf config.documentation.man.generateCaches ''
         ${makewhatis} -T utf8 ${
-          lib.concatMapStringsSep " " (path: "\"$out/${path}\"") cfg.manPath
+          lib.concatMapStringsSep " " (path:
+            "$out/" + lib.escapeShellArg path
+          ) cfg.manPath
         }
       '';
     };
diff --git a/nixos/modules/misc/meta.nix b/nixos/modules/misc/meta.nix
index 8e689a63f6bf..e1d16f802cee 100644
--- a/nixos/modules/misc/meta.nix
+++ b/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.
         '';
@@ -48,7 +48,7 @@ in
         type = docFile;
         internal = true;
         example = "./meta.chapter.xml";
-        description = ''
+        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/nixos/modules/misc/nixops-autoluks.nix b/nixos/modules/misc/nixops-autoluks.nix
index 20c143286afa..221b34f3cc36 100644
--- a/nixos/modules/misc/nixops-autoluks.nix
+++ b/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/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 69967c8a7601..7f44c3f6f3f0 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/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 {
@@ -55,9 +55,46 @@ let
     check = builtins.isAttrs;
   };
 
-  defaultPkgs = import ../../.. {
-    inherit (cfg) config overlays localSystem crossSystem;
-  };
+  # 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;
+
+  # Context for messages
+  hostPlatformLine = optionalString hasHostPlatform "${showOptionWithDefLocs opt.hostPlatform}";
+  buildPlatformLine = optionalString hasBuildPlatform "${showOptionWithDefLocs opt.buildPlatform}";
+
+  legacyOptionsDefined =
+    optional (opt.localSystem.highestPrio < (mkDefault {}).priority) opt.system
+    ++ optional (opt.localSystem.highestPrio < (mkOptionDefault {}).priority) opt.localSystem
+    ++ optional (opt.crossSystem.highestPrio < (mkOptionDefault {}).priority) opt.crossSystem
+    ;
+
+  defaultPkgs =
+    if opt.hostPlatform.isDefined
+    then
+      let isCross = cfg.buildPlatform != cfg.hostPlatform;
+          systemArgs =
+            if isCross
+            then {
+              localSystem = cfg.buildPlatform;
+              crossSystem = cfg.hostPlatform;
+            }
+            else {
+              localSystem = cfg.hostPlatform;
+            };
+      in
+      import ../../.. ({
+        inherit (cfg) config overlays;
+      } // systemArgs)
+    else
+      import ../../.. {
+        inherit (cfg) config overlays localSystem crossSystem;
+      };
 
   finalPkgs = if opt.pkgs.isDefined then cfg.pkgs.appendOverlays cfg.overlays else defaultPkgs;
 
@@ -67,6 +104,7 @@ in
   imports = [
     ./assertions.nix
     ./meta.nix
+    (mkRemovedOptionModule [ "nixpkgs" "initialSystem" ] "The NixOS options `nesting.clone` and `nesting.children` have been deleted, and replaced with named specialisation. Therefore `nixpgks.initialSystem` has no effect anymore.")
   ];
 
   options.nixpkgs = {
@@ -79,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 <code>nixpkgs.overlays</code>, if
-        that is also set. Either <code>nixpkgs.crossSystem</code> or
-        <code>nixpkgs.localSystem</code> 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 <code>nixpkgs.*</code>, notably <code>config</code>,
+        other options in `nixpkgs.*`, notably `config`,
         will be ignored.
 
         If unset, the pkgs argument to all NixOS modules is determined
@@ -94,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 <code>nixos</code> in the default value is in fact a
-        relative path. The <code>config</code>, <code>overlays</code>,
-        <code>localSystem</code>, and <code>crossSystem</code> 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 <code>lib.mkDefault</code>, so
+        their default value using `lib.mkDefault`, so
         user-provided configuration can override it without using
-        <code>lib</code>.
+        `lib`.
 
         Note that using a distinct version of Nixpkgs with NixOS may
         be an unexpected source of problems. Use this option with care.
@@ -119,12 +157,12 @@ in
           { allowBroken = true; allowUnfree = true; }
         '';
       type = configType;
-      description = ''
+      description = lib.mdDoc ''
         The configuration of the Nix Packages collection.  (For
         details, see the Nixpkgs documentation.)  It allows you to set
         package configuration options.
 
-        Ignored when <code>nixpkgs.pkgs</code> is set.
+        Ignored when `nixpkgs.pkgs` is set.
       '';
     };
 
@@ -142,17 +180,57 @@ in
           ]
         '';
       type = types.listOf overlayType;
-      description = ''
+      description = lib.mdDoc ''
         List of overlays to use with the Nix Packages collection.
         (For details, see the Nixpkgs documentation.)  It allows
         you to override packages globally. Each function in the list
-        takes as an argument the <emphasis>original</emphasis> Nixpkgs.
+        takes as an argument the *original* Nixpkgs.
         The first argument should be used for finding dependencies, and
         the second should be used for overriding recipes.
 
-        If <code>nixpkgs.pkgs</code> is set, overlays specified here
+        If `nixpkgs.pkgs` is set, overlays specified here
         will be applied after the overlays that were already present
-        in <code>nixpkgs.pkgs</code>.
+        in `nixpkgs.pkgs`.
+      '';
+    };
+
+    hostPlatform = mkOption {
+      type = types.either types.str types.attrs; # TODO utilize lib.systems.parsedPlatform
+      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      # Make sure that the final value has all fields for sake of other modules
+      # referring to this. TODO make `lib.systems` itself use the module system.
+      apply = lib.systems.elaborate;
+      defaultText = literalExpression
+        ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
+      description = lib.mdDoc ''
+        Specifies the platform where the NixOS configuration will run.
+
+        To cross-compile, set also `nixpkgs.buildPlatform`.
+
+        Ignored when `nixpkgs.pkgs` is set.
+      '';
+    };
+
+    buildPlatform = mkOption {
+      type = types.either types.str types.attrs; # TODO utilize lib.systems.parsedPlatform
+      default = cfg.hostPlatform;
+      example = { system = "x86_64-linux"; config = "x86_64-unknown-linux-gnu"; };
+      # Make sure that the final value has all fields for sake of other modules
+      # referring to this.
+      apply = lib.systems.elaborate;
+      defaultText = literalExpression
+        ''config.nixpkgs.hostPlatform'';
+      description = lib.mdDoc ''
+        Specifies the platform on which NixOS should be built.
+        By default, NixOS is built on the system where it runs, but you can
+        change where it's built. Setting this option will cause NixOS to be
+        cross-compiled.
+
+        For instance, if you're doing distributed multi-platform deployment,
+        or if you're building machines, you can set this to match your
+        development system and/or build farm.
+
+        Ignored when `nixpkgs.pkgs` is set.
       '';
     };
 
@@ -165,73 +243,101 @@ in
       apply = lib.systems.elaborate;
       defaultText = literalExpression
         ''(import "''${nixos}/../lib").lib.systems.examples.aarch64-multiplatform'';
-      description = ''
+      description = lib.mdDoc ''
+        Systems with a recently generated `hardware-configuration.nix`
+        do not need to specify this option, unless cross-compiling, in which case
+        you should set *only* {option}`nixpkgs.buildPlatform`.
+
+        If this is somehow not feasible, you may fall back to removing the
+        {option}`nixpkgs.hostPlatform` line from the generated config and
+        use the old options.
+
         Specifies the platform on which NixOS should be built. When
-        <code>nixpkgs.crossSystem</code> is unset, it also specifies
-        the platform <emphasis>for</emphasis> which NixOS should be
+        `nixpkgs.crossSystem` is unset, it also specifies
+        the platform *for* which NixOS should be
         built.  If this option is unset, it defaults to the platform
         type of the machine where evaluation happens. Specifying this
         option is useful when doing distributed multi-platform
         deployment, or when building virtual machines. See its
         description in the Nixpkgs manual for more details.
 
-        Ignored when <code>nixpkgs.pkgs</code> is set.
+        Ignored when `nixpkgs.pkgs` or `hostPlatform` is set.
       '';
     };
 
+    # TODO deprecate. "crossSystem" is a nonsense identifier, because "cross"
+    #      is a relation between at least 2 systems in the context of a
+    #      specific build step, not a single system.
     crossSystem = mkOption {
       type = types.nullOr types.attrs; # TODO utilize lib.systems.parsedPlatform
       default = null;
       example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
-      description = ''
+      description = lib.mdDoc ''
+        Systems with a recently generated `hardware-configuration.nix`
+        may instead specify *only* {option}`nixpkgs.buildPlatform`,
+        or fall back to removing the {option}`nixpkgs.hostPlatform` line from the generated config.
+
         Specifies the platform for which NixOS should be
         built. Specify this only if it is different from
-        <code>nixpkgs.localSystem</code>, the platform
-        <emphasis>on</emphasis> which NixOS should be built. In other
+        `nixpkgs.localSystem`, the platform
+        *on* which NixOS should be built. In other
         words, specify this to cross-compile NixOS. Otherwise it
         should be set as null, the default. See its description in the
         Nixpkgs manual for more details.
 
-        Ignored when <code>nixpkgs.pkgs</code> is set.
+        Ignored when `nixpkgs.pkgs` or `hostPlatform` is set.
       '';
     };
 
     system = mkOption {
       type = types.str;
       example = "i686-linux";
-      description = ''
+      default =
+        if opt.hostPlatform.isDefined
+        then
+          throw ''
+            Neither ${opt.system} nor any other option in nixpkgs.* is meant
+            to be read by modules and configurations.
+            Use pkgs.stdenv.hostPlatform instead.
+          ''
+        else
+          throw ''
+            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,
+            but will be deprecated in the future, so we recommend to set ${opt.hostPlatform}.
+          '';
+      defaultText = lib.literalMD ''
+        Traditionally `builtins.currentSystem`, but unset when invoking NixOS through `lib.nixosSystem`.
+      '';
+      description = lib.mdDoc ''
+        This option does not need to be specified for NixOS configurations
+        with a recently generated `hardware-configuration.nix`.
+
         Specifies the Nix platform type on which NixOS should be built.
-        It is better to specify <code>nixpkgs.localSystem</code> instead.
-        <programlisting>
+        It is better to specify `nixpkgs.localSystem` instead.
+        ```
         {
           nixpkgs.system = ..;
         }
-        </programlisting>
+        ```
         is the same as
-        <programlisting>
+        ```
         {
           nixpkgs.localSystem.system = ..;
         }
-        </programlisting>
-        See <code>nixpkgs.localSystem</code> for more information.
+        ```
+        See `nixpkgs.localSystem` for more information.
 
-        Ignored when <code>nixpkgs.localSystem</code> is set.
-        Ignored when <code>nixpkgs.pkgs</code> is set.
-      '';
-    };
-
-    initialSystem = mkOption {
-      type = types.str;
-      internal = true;
-      description = ''
-        Preserved value of <literal>system</literal> passed to <literal>eval-config.nix</literal>.
+        Ignored when `nixpkgs.pkgs`, `nixpkgs.localSystem` or `nixpkgs.hostPlatform` is set.
       '';
     };
   };
 
   config = {
     _module.args = {
-      pkgs = finalPkgs;
+      pkgs = finalPkgs.__splicedPackages;
     };
 
     assertions = [
@@ -247,10 +353,23 @@ in
             else "nixpkgs.localSystem";
           pkgsSystem = finalPkgs.stdenv.targetPlatform.system;
         in {
-          assertion = 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 = constructedByMe -> hasPlatform -> legacyOptionsDefined == [];
+        message = ''
+          Your system configures nixpkgs with the platform parameter${optionalString hasBuildPlatform "s"}:
+          ${hostPlatformLine
+          }${buildPlatformLine
+          }
+          However, it also defines the legacy options:
+          ${concatMapStrings showOptionWithDefLocs legacyOptionsDefined}
+          For a future proof system configuration, we recommend to remove
+          the legacy definitions.
+        '';
+      }
     ];
   };
 
diff --git a/nixos/modules/misc/nixpkgs/test.nix b/nixos/modules/misc/nixpkgs/test.nix
index ec5fab9fb4a5..a6d8877ae070 100644
--- a/nixos/modules/misc/nixpkgs/test.nix
+++ b/nixos/modules/misc/nixpkgs/test.nix
@@ -1,8 +1,69 @@
 { evalMinimalConfig, pkgs, lib, stdenv }:
+let
+  eval = mod: evalMinimalConfig {
+    imports = [ ../nixpkgs.nix mod ];
+  };
+  withHost = eval {
+    nixpkgs.hostPlatform = "aarch64-linux";
+  };
+  withHostAndBuild = eval {
+    nixpkgs.hostPlatform = "aarch64-linux";
+    nixpkgs.buildPlatform = "aarch64-darwin";
+  };
+  ambiguous = {
+    _file = "ambiguous.nix";
+    nixpkgs.hostPlatform = "aarch64-linux";
+    nixpkgs.buildPlatform = "aarch64-darwin";
+    nixpkgs.system = "x86_64-linux";
+    nixpkgs.localSystem.system = "x86_64-darwin";
+    nixpkgs.crossSystem.system = "i686-linux";
+    imports = [
+      { _file = "repeat.nix";
+        nixpkgs.hostPlatform = "aarch64-linux";
+      }
+    ];
+  };
+  getErrors = module:
+    let
+      uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; };
+    in map (ass: ass.message) (lib.filter (ass: !ass.assertion) uncheckedEval.config.assertions);
+in
 lib.recurseIntoAttrs {
   invokeNixpkgsSimple =
-    (evalMinimalConfig ({ config, modulesPath, ... }: {
-      imports = [ (modulesPath + "/misc/nixpkgs.nix") ];
+    (eval {
       nixpkgs.system = stdenv.hostPlatform.system;
-    }))._module.args.pkgs.hello;
+    })._module.args.pkgs.hello;
+  assertions =
+    assert withHost._module.args.pkgs.stdenv.hostPlatform.system == "aarch64-linux";
+    assert withHost._module.args.pkgs.stdenv.buildPlatform.system == "aarch64-linux";
+    assert withHostAndBuild._module.args.pkgs.stdenv.hostPlatform.system == "aarch64-linux";
+    assert withHostAndBuild._module.args.pkgs.stdenv.buildPlatform.system == "aarch64-darwin";
+    assert builtins.trace (lib.head (getErrors ambiguous))
+      getErrors ambiguous ==
+        [''
+          Your system configures nixpkgs with the platform parameters:
+          nixpkgs.hostPlatform, with values defined in:
+            - repeat.nix
+            - ambiguous.nix
+          nixpkgs.buildPlatform, with values defined in:
+            - ambiguous.nix
+
+          However, it also defines the legacy options:
+          nixpkgs.system, with values defined in:
+            - ambiguous.nix
+          nixpkgs.localSystem, with values defined in:
+            - ambiguous.nix
+          nixpkgs.crossSystem, with values defined in:
+            - ambiguous.nix
+
+          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;
+      } == [];
+
+    pkgs.emptyFile;
 }
diff --git a/nixos/modules/misc/passthru.nix b/nixos/modules/misc/passthru.nix
index 4e99631fdd85..beb9d7829037 100644
--- a/nixos/modules/misc/passthru.nix
+++ b/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/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index 931201ade293..b3cdaf5568d4 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -13,7 +13,7 @@ let
   attrsToText = attrs:
     concatStringsSep "\n" (
       mapAttrsToList (n: v: ''${n}=${escapeIfNeccessary (toString v)}'') attrs
-    );
+    ) + "\n";
 
   osReleaseContents = {
     NAME = "NixOS";
@@ -38,53 +38,60 @@ let
 in
 {
   imports = [
+    ./label.nix
     (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ])
     (mkRenamedOptionModule [ "system" "nixosVersionSuffix" ] [ "system" "nixos" "versionSuffix" ])
     (mkRenamedOptionModule [ "system" "nixosRevision" ] [ "system" "nixos" "revision" ])
     (mkRenamedOptionModule [ "system" "nixosLabel" ] [ "system" "nixos" "label" ])
   ];
 
+  options.boot.initrd.osRelease = mkOption {
+    internal = true;
+    readOnly = true;
+    default = initrdRelease;
+  };
+
   options.system = {
 
     nixos.version = mkOption {
       internal = true;
       type = types.str;
-      description = "The full NixOS version (e.g. <literal>16.03.1160.f2d4ee1</literal>).";
+      description = lib.mdDoc "The full NixOS version (e.g. `16.03.1160.f2d4ee1`).";
     };
 
     nixos.release = mkOption {
       readOnly = true;
       type = types.str;
       default = trivial.release;
-      description = "The NixOS release (e.g. <literal>16.03</literal>).";
+      description = lib.mdDoc "The NixOS release (e.g. `16.03`).";
     };
 
     nixos.versionSuffix = mkOption {
       internal = true;
       type = types.str;
       default = trivial.versionSuffix;
-      description = "The NixOS version suffix (e.g. <literal>1160.f2d4ee1</literal>).";
+      description = lib.mdDoc "The NixOS version suffix (e.g. `1160.f2d4ee1`).";
     };
 
     nixos.revision = mkOption {
       internal = true;
       type = types.nullOr types.str;
       default = trivial.revisionWithDefault null;
-      description = "The Git revision from which this NixOS configuration was built.";
+      description = lib.mdDoc "The Git revision from which this NixOS configuration was built.";
     };
 
     nixos.codeName = mkOption {
       readOnly = true;
       type = types.str;
       default = trivial.codeName;
-      description = "The NixOS release code name (e.g. <literal>Emu</literal>).";
+      description = lib.mdDoc "The NixOS release code name (e.g. `Emu`).";
     };
 
     stateVersion = mkOption {
       type = types.str;
       default = cfg.release;
       defaultText = literalExpression "config.${opt.release}";
-      description = ''
+      description = lib.mdDoc ''
         Every once in a while, a new NixOS release may change
         configuration defaults in a way incompatible with stateful
         data. For instance, if the default version of PostgreSQL
@@ -108,13 +115,13 @@ in
       internal = true;
       type = types.str;
       default = "https://nixos.org/channels/nixos-unstable";
-      description = "Default NixOS channel to which the root user is subscribed.";
+      description = lib.mdDoc "Default NixOS channel to which the root user is subscribed.";
     };
 
     configurationRevision = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "The Git revision of the top-level flake from which this configuration was built.";
+      description = lib.mdDoc "The Git revision of the top-level flake from which this configuration was built.";
     };
 
   };
@@ -142,10 +149,14 @@ in
       "os-release".text = attrsToText osReleaseContents;
     };
 
-    boot.initrd.systemd.contents = {
-      "/etc/os-release".source = initrdRelease;
-      "/etc/initrd-release".source = initrdRelease;
-    };
+    # 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/nixos/modules/misc/wordlist.nix b/nixos/modules/misc/wordlist.nix
index 988b522d7431..f01fcb6f5a91 100644
--- a/nixos/modules/misc/wordlist.nix
+++ b/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/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index dcd9bb8aff1d..ac40b6cbfd97 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1,26 +1,20 @@
 [
+  ./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/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
   ./config/ldap.nix
   ./config/locale.nix
   ./config/malloc.nix
+  ./config/mysql.nix
   ./config/networking.nix
   ./config/no-x-libs.nix
   ./config/nsswitch.nix
@@ -37,26 +31,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/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/teck.nix
+  ./hardware/keyboard/uhk.nix
   ./hardware/keyboard/zsa.nix
   ./hardware/ksm.nix
   ./hardware/ledger.nix
@@ -65,32 +67,37 @@
   ./hardware/network/ath-user-regd.nix
   ./hardware/network/b43.nix
   ./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/wooting.nix
   ./hardware/xone.nix
   ./hardware/xpadneo.nix
   ./i18n/input-method/default.nix
@@ -98,46 +105,48 @@
   ./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
   ./programs/captive-browser.nix
   ./programs/ccache.nix
   ./programs/cdemu.nix
+  ./programs/cfs-zen-tweaks.nix
   ./programs/chromium.nix
-  ./programs/clickshare.nix
   ./programs/cnping.nix
   ./programs/command-not-found/command-not-found.nix
   ./programs/criu.nix
@@ -150,68 +159,80 @@
   ./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/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/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/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/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/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/phosh.nix
   ./programs/qt5ct.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/singularity.nix
+  ./programs/skim.nix
+  ./programs/slock.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/tsm-client.nix
@@ -224,6 +245,7 @@
   ./programs/weylus.nix
   ./programs/wireshark.nix
   ./programs/wshowkeys.nix
+  ./programs/xfconf.nix
   ./programs/xfs_quota.nix
   ./programs/xonsh.nix
   ./programs/xss-lock.nix
@@ -231,10 +253,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
@@ -243,22 +265,23 @@
   ./security/ca.nix
   ./security/chromium-suid-sandbox.nix
   ./security/dhparams.nix
+  ./security/doas.nix
   ./security/duosec.nix
   ./security/google_oslogin.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
@@ -273,17 +296,17 @@
   ./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/ympd.nix
   ./services/backup/automysqlbackup.nix
   ./services/backup/bacula.nix
@@ -295,8 +318,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
@@ -304,13 +327,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
@@ -320,6 +345,7 @@
   ./services/cluster/kubernetes/proxy.nix
   ./services/cluster/kubernetes/scheduler.nix
   ./services/cluster/pacemaker/default.nix
+  ./services/cluster/patroni/default.nix
   ./services/cluster/spark/default.nix
   ./services/computing/boinc/client.nix
   ./services/computing/foldingathome/client.nix
@@ -329,13 +355,14 @@
   ./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/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
@@ -344,9 +371,11 @@
   ./services/databases/clickhouse.nix
   ./services/databases/cockroachdb.nix
   ./services/databases/couchdb.nix
+  ./services/databases/dgraph.nix
+  ./services/databases/dragonflydb.nix
   ./services/databases/firebird.nix
   ./services/databases/foundationdb.nix
-  ./services/databases/hbase.nix
+  ./services/databases/hbase-standalone.nix
   ./services/databases/influxdb.nix
   ./services/databases/influxdb2.nix
   ./services/databases/memcached.nix
@@ -359,7 +388,7 @@
   ./services/databases/pgmanage.nix
   ./services/databases/postgresql.nix
   ./services/databases/redis.nix
-  ./services/databases/riak.nix
+  ./services/databases/surrealdb.nix
   ./services/databases/victoriametrics.nix
   ./services/desktops/accountsservice.nix
   ./services/desktops/bamf.nix
@@ -370,16 +399,10 @@
   ./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
@@ -389,27 +412,33 @@
   ./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-media-session.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/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/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
@@ -424,6 +453,8 @@
   ./services/games/terraria.nix
   ./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
@@ -436,9 +467,11 @@
   ./services/hardware/interception-tools.nix
   ./services/hardware/irqbalance.nix
   ./services/hardware/joycond.nix
+  ./services/hardware/kanata.nix
   ./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
@@ -449,21 +482,22 @@
   ./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/xow.nix
+  ./services/home-automation/evcc.nix
   ./services/home-automation/home-assistant.nix
   ./services/home-automation/zigbee2mqtt.nix
   ./services/logging/SystemdJournal2Gelf.nix
@@ -490,61 +524,69 @@
   ./services/mail/dovecot.nix
   ./services/mail/dspam.nix
   ./services/mail/exim.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/spamassassin.nix
+  ./services/mail/postsrsd.nix
+  ./services/mail/public-inbox.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/matrix/matrix-synapse.nix
+  ./services/matrix/appservice-discord.nix
+  ./services/matrix/appservice-irc.nix
+  ./services/matrix/conduit.nix
+  ./services/matrix/dendrite.nix
+  ./services/matrix/mautrix-facebook.nix
+  ./services/matrix/mautrix-telegram.nix
   ./services/matrix/mjolnir.nix
   ./services/matrix/pantalaimon.nix
-  ./services/misc/ananicy.nix
+  ./services/matrix/synapse.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/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/dendrite.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
@@ -552,33 +594,29 @@
   ./services/misc/gammu-smsd.nix
   ./services/misc/geoipupdate.nix
   ./services/misc/gitea.nix
-  #./services/misc/gitit.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/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/matrix-appservice-discord.nix
-  ./services/misc/matrix-appservice-irc.nix
-  ./services/misc/matrix-conduit.nix
-  ./services/misc/mautrix-facebook.nix
-  ./services/misc/mautrix-telegram.nix
   ./services/misc/mbpfan.nix
   ./services/misc/mediatomb.nix
   ./services/misc/metabase.nix
@@ -591,6 +629,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
@@ -600,21 +639,23 @@
   ./services/misc/packagekit.nix
   ./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/pykms.nix
   ./services/misc/radarr.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/safeeyes.nix
   ./services/misc/sdrplay.nix
+  ./services/misc/serviio.nix
   ./services/misc/sickbeard.nix
   ./services/misc/signald.nix
   ./services/misc/siproxd.nix
@@ -622,6 +663,7 @@
   ./services/misc/sonarr.nix
   ./services/misc/sourcehut
   ./services/misc/spice-vdagentd.nix
+  ./services/misc/spice-webdavd.nix
   ./services/misc/ssm-agent.nix
   ./services/misc/sssd.nix
   ./services/misc/subsonic.nix
@@ -629,7 +671,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
@@ -647,73 +691,81 @@
   ./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
   ./services/monitoring/metricbeat.nix
+  ./services/monitoring/mimir.nix
   ./services/monitoring/monit.nix
   ./services/monitoring/munin.nix
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
   ./services/monitoring/parsedmarc.nix
-  ./services/monitoring/prometheus/default.nix
   ./services/monitoring/prometheus/alertmanager.nix
+  ./services/monitoring/prometheus/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/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/adguardhome.nix
   ./services/networking/amuled.nix
@@ -721,20 +773,24 @@
   ./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-lg.nix
   ./services/networking/bird.nix
+  ./services/networking/bitcoind.nix
   ./services/networking/bitlbee.nix
   ./services/networking/blockbook-frontend.nix
   ./services/networking/blocky.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
@@ -753,16 +809,16 @@
   ./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
   ./services/networking/ergo.nix
   ./services/networking/ergochat.nix
   ./services/networking/eternal-terminal.nix
+  ./services/networking/expressvpn.nix
   ./services/networking/fakeroute.nix
   ./services/networking/ferm.nix
+  ./services/networking/firefox-syncserver.nix
   ./services/networking/fireqos.nix
   ./services/networking/firewall.nix
   ./services/networking/flannel.nix
@@ -775,6 +831,7 @@
   ./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
@@ -786,10 +843,10 @@
   ./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/inspircd.nix
   ./services/networking/iodine.nix
   ./services/networking/iperf3.nix
@@ -810,17 +867,19 @@
   ./services/networking/libreswan.nix
   ./services/networking/lldpd.nix
   ./services/networking/logmein-hamachi.nix
+  ./services/networking/lokinet.nix
   ./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
@@ -833,17 +892,20 @@
   ./services/networking/nat.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/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
@@ -859,81 +921,87 @@
   ./services/networking/openvpn.nix
   ./services/networking/ostinato.nix
   ./services/networking/owamp.nix
+  ./services/networking/pdns-recursor.nix
   ./services/networking/pdnsd.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
   ./services/networking/rdnssd.nix
   ./services/networking/redsocks.nix
   ./services/networking/resilio.nix
   ./services/networking/robustirc-bridge.nix
+  ./services/networking/routedns.nix
   ./services/networking/rpcbind.nix
   ./services/networking/rxe.nix
   ./services/networking/sabnzbd.nix
   ./services/networking/seafile.nix
   ./services/networking/searx.nix
-  ./services/networking/skydns.nix
   ./services/networking/shadowsocks.nix
   ./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/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
@@ -941,10 +1009,11 @@
   ./services/networking/wg-quick.nix
   ./services/networking/wireguard.nix
   ./services/networking/wpa_supplicant.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
@@ -952,11 +1021,12 @@
   ./services/networking/zerotierone.nix
   ./services/networking/znc/default.nix
   ./services/printing/cupsd.nix
+  ./services/printing/ipp-usb.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
@@ -965,20 +1035,25 @@
   ./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/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/privacyidea.nix
+  ./services/security/pass-secret-service.nix
   ./services/security/physlock.nix
+  ./services/security/privacyidea.nix
   ./services/security/shibboleth-sp.nix
   ./services/security/sks.nix
   ./services/security/sshguard.nix
@@ -991,12 +1066,14 @@
   ./services/security/vault.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/localtime.nix
   ./services/system/kerberos/default.nix
+  ./services/system/localtimed.nix
   ./services/system/nscd.nix
   ./services/system/saslauthd.nix
   ./services/system/self-deploy.nix
@@ -1009,47 +1086,56 @@
   ./services/torrent/peerflix.nix
   ./services/torrent/rtorrent.nix
   ./services/torrent/transmission.nix
+  ./services/tracing/tempo.nix
   ./services/ttys/getty.nix
   ./services/ttys/gpm.nix
   ./services/ttys/kmscon.nix
-  ./services/wayland/cage.nix
   ./services/video/epgstation/default.nix
   ./services/video/mirakurun.nix
   ./services/video/replay-sorcery.nix
+  ./services/video/rtsp-simple-server.nix
+  ./services/video/unifi-video.nix
+  ./services/wayland/cage.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/baget.nix
   ./services/web-apps/bookstack.nix
   ./services/web-apps/calibre-web.nix
+  ./services/web-apps/changedetection-io.nix
   ./services/web-apps/code-server.nix
-  ./services/web-apps/baget.nix
   ./services/web-apps/convos.nix
-  ./services/web-apps/cryptpad.nix
   ./services/web-apps/dex.nix
   ./services/web-apps/discourse.nix
   ./services/web-apps/documize.nix
   ./services/web-apps/dokuwiki.nix
+  ./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/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/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/mastodon.nix
+  ./services/web-apps/matomo.nix
   ./services/web-apps/mattermost.nix
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
@@ -1059,27 +1145,31 @@
   ./services/web-apps/nexus.nix
   ./services/web-apps/nifi.nix
   ./services/web-apps/node-red.nix
-  ./services/web-apps/pict-rs.nix
+  ./services/web-apps/onlyoffice.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/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/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/virtlyst.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
@@ -1087,13 +1177,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
@@ -1101,20 +1194,16 @@
   ./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/tomcat.nix
   ./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
@@ -1124,24 +1213,30 @@
   ./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/none.nix
   ./services/x11/window-managers/twm.nix
@@ -1153,13 +1248,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
@@ -1168,6 +1265,7 @@
   ./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
@@ -1182,15 +1280,18 @@
   ./system/boot/stage-2.nix
   ./system/boot/systemd.nix
   ./system/boot/systemd/coredump.nix
+  ./system/boot/systemd/initrd-secrets.nix
+  ./system/boot/systemd/initrd.nix
   ./system/boot/systemd/journald.nix
   ./system/boot/systemd/logind.nix
   ./system/boot/systemd/nspawn.nix
+  ./system/boot/systemd/oomd.nix
   ./system/boot/systemd/shutdown.nix
   ./system/boot/systemd/tmpfiles.nix
   ./system/boot/systemd/user.nix
-  ./system/boot/systemd/initrd.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
@@ -1215,44 +1316,48 @@
   ./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/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/railcar.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/nixos/modules/profiles/all-hardware.nix b/nixos/modules/profiles/all-hardware.nix
index 8347453d403b..4857ea4dbeae 100644
--- a/nixos/modules/profiles/all-hardware.nix
+++ b/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.
@@ -57,7 +57,7 @@ in
 
       # Hyper-V support.
       "hv_storvsc"
-    ] ++ lib.optionals (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
+    ] ++ lib.optionals pkgs.stdenv.hostPlatform.isAarch [
       # Most of the following falls into two categories:
       #  - early KMS / early display
       #  - early storage (e.g. USB) support
@@ -109,6 +109,9 @@ in
       # USB drivers
       "xhci-pci-renesas"
 
+      # Reset controllers
+      "reset-raspberrypi" # Triggers USB chip firmware load.
+
       # Misc "weak" dependencies
       "analogix-dp"
       "analogix-anx6345" # For DP or eDP (e.g. integrated display)
diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix
index 33dd80d7c5ab..616b2470dcb4 100644
--- a/nixos/modules/profiles/base.nix
+++ b/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.
 
-{ lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 {
   # Include some utilities that are useful for installing or repairing
@@ -20,7 +20,13 @@
     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
@@ -36,6 +42,7 @@
     pkgs.smartmontools # for diagnosing hard disks
     pkgs.pciutils
     pkgs.usbutils
+    pkgs.nvme-cli
 
     # Tools to create / manipulate filesystems.
     pkgs.ntfsprogs # for resizing NTFS partitions
@@ -51,7 +58,9 @@
   ];
 
   # Include support for various filesystems.
-  boot.supportedFilesystems = [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "zfs" "ntfs" "cifs" ];
+  boot.supportedFilesystems =
+    [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "ntfs" "cifs" ] ++
+    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/nixos/modules/profiles/clone-config.nix b/nixos/modules/profiles/clone-config.nix
index 3f669ba7d2e1..ba65a250d25a 100644
--- a/nixos/modules/profiles/clone-config.nix
+++ b/nixos/modules/profiles/clone-config.nix
@@ -61,7 +61,7 @@ in
 
     installer.cloneConfig = mkOption {
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Try to clone the installation-device configuration by re-using it's
         profile from the list of imported modules.
       '';
@@ -70,14 +70,14 @@ in
     installer.cloneConfigIncludes = mkOption {
       default = [];
       example = [ "./nixos/modules/hardware/network/rt73.nix" ];
-      description = ''
+      description = lib.mdDoc ''
         List of modules used to re-build this installation device profile.
       '';
     };
 
     installer.cloneConfigExtra = mkOption {
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra text to include in the cloned configuration.nix included in this
         installer.
       '';
diff --git a/nixos/modules/profiles/docker-container.nix b/nixos/modules/profiles/docker-container.nix
index 183645de36fb..5365e49711dc 100644
--- a/nixos/modules/profiles/docker-container.nix
+++ b/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/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index 3c503fba2a39..ae9be08c8d85 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -22,10 +22,10 @@ with lib;
   config = {
 
     # 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 +41,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.
@@ -99,6 +99,10 @@ with lib;
         stdenvNoCC # for runCommand
         busybox
         jq # for closureInfo
+        # For boot.initrd.systemd
+        makeInitrdNGTool
+        systemdStage1
+        systemdStage1Network
       ];
 
     # Show all debug messages from the kernel but don't log refused packets
diff --git a/nixos/modules/profiles/keys/ssh_host_ed25519_key b/nixos/modules/profiles/keys/ssh_host_ed25519_key
new file mode 100644
index 000000000000..b18489795369
--- /dev/null
+++ b/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/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub b/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
new file mode 100644
index 000000000000..2c45826715fc
--- /dev/null
+++ b/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBWcxb/Blaqt1auOtE+F8QUWrUotiC5qBJ+UuEWdVCb root@nixos
diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix
new file mode 100644
index 000000000000..895dd04cb485
--- /dev/null
+++ b/nixos/modules/profiles/macos-builder.nix
@@ -0,0 +1,134 @@
+{ config, pkgs, ... }:
+
+let
+  keysDirectory = "/var/keys";
+
+  user = "builder";
+
+  keyType = "ed25519";
+
+in
+
+{ imports = [
+    ../virtualisation/qemu-vm.nix
+  ];
+
+  # 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 = 1024 * 1024 * 1024;
+
+    max-free = 3 * 1024 * 1024 * 1024;
+
+    trusted-users = [ "root" user ];
+  };
+
+  services.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 = pkgs.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;
+
+    in
+      hostPkgs.writeShellScriptBin "create-builder" ''
+        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="$(nix-store --add "$KEYS")" ${config.system.build.vm}/bin/run-nixos-vm
+      '';
+
+  system.stateVersion = "22.05";
+
+  users.users."${user}"= {
+    isNormalUser = true;
+  };
+
+  virtualisation = {
+    diskSize = 20 * 1024;
+
+    memorySize = 3 * 1024;
+
+    forwardPorts = [
+      { from = "host"; guest.port = 22; host.port = 22; }
+    ];
+
+    # 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/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix
index e79b92723841..bd1b2b452189 100644
--- a/nixos/modules/profiles/minimal.nix
+++ b/nixos/modules/profiles/minimal.nix
@@ -8,12 +8,24 @@ with lib;
 {
   environment.noXlibs = mkDefault true;
 
-  # This isn't perfect, but let's expect the user specifies an UTF-8 defaultLocale
-  i18n.supportedLocales = [ (config.i18n.defaultLocale + "/UTF-8") ];
-
   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/nixos/modules/profiles/qemu-guest.nix b/nixos/modules/profiles/qemu-guest.nix
index d4335edfcf2d..8b3df97ae0db 100644
--- a/nixos/modules/profiles/qemu-guest.nix
+++ b/nixos/modules/profiles/qemu-guest.nix
@@ -1,13 +1,13 @@
 # Common configuration for virtual machines running under QEMU (using
 # virtio).
 
-{ ... }:
+{ config, lib, ... }:
 
 {
   boot.initrd.availableKernelModules = [ "virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "9p" "9pnet_virtio" ];
   boot.initrd.kernelModules = [ "virtio_balloon" "virtio_console" "virtio_rng" ];
 
-  boot.initrd.postDeviceCommands =
+  boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
     ''
       # Set the system time from the hardware clock to work around a
       # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised
diff --git a/nixos/modules/programs/_1password-gui.nix b/nixos/modules/programs/_1password-gui.nix
index 42f6a0b52252..83ef6037fb5a 100644
--- a/nixos/modules/programs/_1password-gui.nix
+++ b/nixos/modules/programs/_1password-gui.nix
@@ -8,24 +8,21 @@ let
 
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "programs" "_1password-gui" "gid" ] ''
+      A preallocated GID will be used instead.
+    '')
+  ];
+
   options = {
     programs._1password-gui = {
-      enable = mkEnableOption "the 1Password GUI application";
-
-      gid = mkOption {
-        type = types.addCheck types.int (x: x >= 1000);
-        example = literalExpression "5000";
-        description = ''
-          The gid to assign to the onepassword group, which is needed for browser integration.
-          It must be 1000 or greater.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "the 1Password GUI application");
 
       polkitPolicyOwners = mkOption {
         type = types.listOf types.str;
         default = [ ];
         example = literalExpression ''["user1" "user2" "user3"]'';
-        description = ''
+        description = lib.mdDoc ''
           A list of users who should be able to integrate 1Password with polkit-based authentication mechanisms.
         '';
       };
@@ -44,7 +41,7 @@ in
     in
     mkIf cfg.enable {
       environment.systemPackages = [ package ];
-      users.groups.onepassword.gid = cfg.gid;
+      users.groups.onepassword.gid = config.ids.gids.onepassword;
 
       security.wrappers = {
         "1Password-BrowserSupport" = {
diff --git a/nixos/modules/programs/_1password.nix b/nixos/modules/programs/_1password.nix
index 547c12867a91..91246150755d 100644
--- a/nixos/modules/programs/_1password.nix
+++ b/nixos/modules/programs/_1password.nix
@@ -8,18 +8,15 @@ let
 
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "programs" "_1password" "gid" ] ''
+      A preallocated GID will be used instead.
+    '')
+  ];
+
   options = {
     programs._1password = {
-      enable = mkEnableOption "the 1Password CLI tool";
-
-      gid = mkOption {
-        type = types.addCheck types.int (x: x >= 1000);
-        example = literalExpression "5001";
-        description = ''
-          The gid to assign to the onepassword-cli group, which is needed for integration with the 1Password GUI.
-          It must be 1000 or greater.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "the 1Password CLI tool");
 
       package = mkPackageOption pkgs "1Password CLI" {
         default = [ "_1password" ];
@@ -29,7 +26,7 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ cfg.package ];
-    users.groups.onepassword-cli.gid = cfg.gid;
+    users.groups.onepassword-cli.gid = config.ids.gids.onepassword-cli;
 
     security.wrappers = {
       "op" = {
diff --git a/nixos/modules/programs/adb.nix b/nixos/modules/programs/adb.nix
index 9e9e37f92a87..e5b0abd9fcfe 100644
--- a/nixos/modules/programs/adb.nix
+++ b/nixos/modules/programs/adb.nix
@@ -11,10 +11,10 @@ with lib;
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure system to use Android Debug Bridge (adb).
           To grant access to a user, it must be part of adbusers group:
-          <code>users.users.alice.extraGroups = ["adbusers"];</code>
+          `users.users.alice.extraGroups = ["adbusers"];`
         '';
       };
     };
diff --git a/nixos/modules/programs/appgate-sdp.nix b/nixos/modules/programs/appgate-sdp.nix
index 12cb542f4d04..bdd538dc2f1f 100644
--- a/nixos/modules/programs/appgate-sdp.nix
+++ b/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/nixos/modules/programs/atop.nix b/nixos/modules/programs/atop.nix
index ad75ab27666c..2b14d7c73439 100644
--- a/nixos/modules/programs/atop.nix
+++ b/nixos/modules/programs/atop.nix
@@ -14,13 +14,13 @@ in
 
     programs.atop = rec {
 
-      enable = mkEnableOption "Atop";
+      enable = mkEnableOption (lib.mdDoc "Atop");
 
       package = mkOption {
         type = types.package;
         default = pkgs.atop;
         defaultText = literalExpression "pkgs.atop";
-        description = ''
+        description = lib.mdDoc ''
           Which package to use for Atop.
         '';
       };
@@ -29,7 +29,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to install and enable the netatop kernel module.
             Note: this sets the kernel taint flag "O" for loading out-of-tree modules.
           '';
@@ -38,7 +38,7 @@ in
           type = types.package;
           default = config.boot.kernelPackages.netatop;
           defaultText = literalExpression "config.boot.kernelPackages.netatop";
-          description = ''
+          description = lib.mdDoc ''
             Which package to use for netatop.
           '';
         };
@@ -47,7 +47,7 @@ in
       atopgpu.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install and enable the atopgpud daemon to get information about
           NVIDIA gpus.
         '';
@@ -56,7 +56,7 @@ in
       setuidWrapper.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install a setuid wrapper for Atop. This is required to use some of
           the features as non-root user (e.g.: ipc information, netatop, atopgpu).
           Atop tries to drop the root privileges shortly after starting.
@@ -66,7 +66,7 @@ in
       atopService.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the atop service responsible for storing statistics for
           long-term analysis.
         '';
@@ -74,7 +74,7 @@ in
       atopRotateTimer.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the atop-rotate timer, which restarts the atop service
           daily to make sure the data files are rotate.
         '';
@@ -82,7 +82,7 @@ in
       atopacctService.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the atopacct service which manages process accounting.
           This allows Atop to gather data about processes that disappeared in between
           two refresh intervals.
@@ -95,8 +95,8 @@ in
           flags = "a1f";
           interval = 5;
         };
-        description = ''
-          Parameters to be written to <filename>/etc/atoprc</filename>.
+        description = lib.mdDoc ''
+          Parameters to be written to {file}`/etc/atoprc`.
         '';
       };
     };
@@ -136,6 +136,24 @@ in
           packages = [ atop (lib.mkIf cfg.netatop.enable cfg.netatop.package) ];
           services =
             mkService cfg.atopService.enable "atop" [ atop ]
+            // lib.mkIf cfg.atopService.enable {
+              # always convert logs to newer version first
+              # XXX might trigger TimeoutStart but restarting atop.service will
+              # convert remainings logs and start eventually
+              atop.serviceConfig.ExecStartPre = pkgs.writeShellScript "atop-update-log-format" ''
+                set -e -u
+                for logfile in "$LOGPATH"/atop_*
+                do
+                  ${atop}/bin/atopconvert "$logfile" "$logfile".new
+                  # only replace old file if version was upgraded to avoid
+                  # false positives for atop-rotate.service
+                  if ! ${pkgs.diffutils}/bin/cmp -s "$logfile" "$logfile".new
+                  then
+                    ${pkgs.coreutils}/bin/mv -v -f "$logfile".new "$logfile"
+                  fi
+                done
+              '';
+            }
             // mkService cfg.atopacctService.enable "atopacct" [ atop ]
             // mkService cfg.netatop.enable "netatop" [ cfg.netatop.package ]
             // mkService cfg.atopgpu.enable "atopgpu" [ atop ];
diff --git a/nixos/modules/programs/ausweisapp.nix b/nixos/modules/programs/ausweisapp.nix
new file mode 100644
index 000000000000..ef1f059568c6
--- /dev/null
+++ b/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/nixos/modules/programs/autojump.nix b/nixos/modules/programs/autojump.nix
index ecfc2f658079..dde6870d9890 100644
--- a/nixos/modules/programs/autojump.nix
+++ b/nixos/modules/programs/autojump.nix
@@ -13,7 +13,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable autojump.
         '';
       };
diff --git a/nixos/modules/programs/bandwhich.nix b/nixos/modules/programs/bandwhich.nix
index 610d602ad2cc..8d1612217ad8 100644
--- a/nixos/modules/programs/bandwhich.nix
+++ b/nixos/modules/programs/bandwhich.nix
@@ -11,7 +11,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add bandwhich to the global environment and configure a
           setcap wrapper for it.
         '';
diff --git a/nixos/modules/programs/bash-my-aws.nix b/nixos/modules/programs/bash-my-aws.nix
index 15e429a75497..10f16cae651b 100644
--- a/nixos/modules/programs/bash-my-aws.nix
+++ b/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/nixos/modules/programs/bash/bash-completion.nix b/nixos/modules/programs/bash/bash-completion.nix
index b8e5b1bfa336..96fbe0126d66 100644
--- a/nixos/modules/programs/bash/bash-completion.nix
+++ b/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/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 7281126979e5..286faeadc489 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/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)
   );
 
@@ -30,10 +30,10 @@ in
       /*
       enable = mkOption {
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whenever to configure Bash as an interactive shell.
           Note that this tries to make Bash the default
-          <option>users.defaultUserShell</option>,
+          {option}`users.defaultUserShell`,
           which in turn means that you might need to explicitly
           set this variable if you have another shell configured
           with NixOS.
@@ -44,16 +44,16 @@ in
 
       shellAliases = mkOption {
         default = {};
-        description = ''
-          Set of aliases for bash shell, which overrides <option>environment.shellAliases</option>.
-          See <option>environment.shellAliases</option> for an option format description.
+        description = lib.mdDoc ''
+          Set of aliases for bash shell, which overrides {option}`environment.shellAliases`.
+          See {option}`environment.shellAliases` for an option format description.
         '';
         type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during bash shell initialisation.
         '';
         type = types.lines;
@@ -61,7 +61,7 @@ in
 
       loginShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during login bash shell initialisation.
         '';
         type = types.lines;
@@ -69,7 +69,7 @@ in
 
       interactiveShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during interactive bash shell initialisation.
         '';
         type = types.lines;
@@ -92,7 +92,7 @@ in
             fi
           fi
         '';
-        description = ''
+        description = lib.mdDoc ''
           Shell script code used to initialise the bash prompt.
         '';
         type = types.lines;
@@ -100,7 +100,7 @@ in
 
       promptPluginInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code used to initialise bash prompt plugins.
         '';
         type = types.lines;
diff --git a/nixos/modules/programs/bash/blesh.nix b/nixos/modules/programs/bash/blesh.nix
new file mode 100644
index 000000000000..8fa51bef7744
--- /dev/null
+++ b/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/nixos/modules/programs/bash/ls-colors.nix b/nixos/modules/programs/bash/ls-colors.nix
index 254ee14c477d..6a5253a3cca2 100644
--- a/nixos/modules/programs/bash/ls-colors.nix
+++ b/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/nixos/modules/programs/bash/undistract-me.nix b/nixos/modules/programs/bash/undistract-me.nix
index 0e6465e048a1..587b649377df 100644
--- a/nixos/modules/programs/bash/undistract-me.nix
+++ b/nixos/modules/programs/bash/undistract-me.nix
@@ -8,13 +8,13 @@ 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;
-        description = ''
+        description = lib.mdDoc ''
           Number of seconds it would take for a command to be considered long-running.
         '';
         type = types.int;
diff --git a/nixos/modules/programs/bcc.nix b/nixos/modules/programs/bcc.nix
index e475c6ceaa6c..ff29d56bedb9 100644
--- a/nixos/modules/programs/bcc.nix
+++ b/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/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix
index e1456d3c1848..346d38e5e880 100644
--- a/nixos/modules/programs/browserpass.nix
+++ b/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/nixos/modules/programs/calls.nix b/nixos/modules/programs/calls.nix
index 08a223b408d4..7a18982915a9 100644
--- a/nixos/modules/programs/calls.nix
+++ b/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/nixos/modules/programs/captive-browser.nix b/nixos/modules/programs/captive-browser.nix
index aad554c2bd66..36ceb1a69610 100644
--- a/nixos/modules/programs/captive-browser.nix
+++ b/nixos/modules/programs/captive-browser.nix
@@ -34,18 +34,18 @@ in
 
   options = {
     programs.captive-browser = {
-      enable = mkEnableOption "captive browser";
+      enable = mkEnableOption (lib.mdDoc "captive browser");
 
       package = mkOption {
         type = types.package;
         default = pkgs.captive-browser;
         defaultText = literalExpression "pkgs.captive-browser";
-        description = "Which package to use for captive-browser";
+        description = lib.mdDoc "Which package to use for captive-browser";
       };
 
       interface = mkOption {
         type = types.str;
-        description = "your public network interface (wlp3s0, wlan0, eth0, ...)";
+        description = lib.mdDoc "your public network interface (wlp3s0, wlan0, eth0, ...)";
       };
 
       # the options below are the same as in "captive-browser.toml"
@@ -53,7 +53,7 @@ in
         type = types.str;
         default = browserDefault pkgs.chromium;
         defaultText = literalExpression (browserDefault "\${pkgs.chromium}");
-        description = ''
+        description = lib.mdDoc ''
           The shell (/bin/sh) command executed once the proxy starts.
           When browser exits, the proxy exits. An extra env var PROXY is available.
 
@@ -69,7 +69,7 @@ in
 
       dhcp-dns = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The shell (/bin/sh) command executed to obtain the DHCP
           DNS server address. The first match of an IPv4 regex is used.
           IPv4 only, because let's be real, it's a captive portal.
@@ -79,15 +79,15 @@ in
       socks5-addr = mkOption {
         type = types.str;
         default = "localhost:1666";
-        description = "the listen address for the SOCKS5 proxy server";
+        description = lib.mdDoc "the listen address for the SOCKS5 proxy server";
       };
 
       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.
         '';
       };
@@ -98,7 +98,7 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [
-      (pkgs.runCommandNoCC "captive-browser-desktop-item" { } ''
+      (pkgs.runCommand "captive-browser-desktop-item" { } ''
         install -Dm444 -t $out/share/applications ${desktopItem}/share/applications/*.desktop
       '')
     ];
diff --git a/nixos/modules/programs/ccache.nix b/nixos/modules/programs/ccache.nix
index 0f7fd0a3683c..19fb7ca3294e 100644
--- a/nixos/modules/programs/ccache.nix
+++ b/nixos/modules/programs/ccache.nix
@@ -6,16 +6,16 @@ let
 in {
   options.programs.ccache = {
     # host configuration
-    enable = mkEnableOption "CCache";
+    enable = mkEnableOption (lib.mdDoc "CCache");
     cacheDir = mkOption {
       type = types.path;
-      description = "CCache directory";
+      description = lib.mdDoc "CCache directory";
       default = "/var/cache/ccache";
     };
     # target configuration
     packageNames = mkOption {
       type = types.listOf types.str;
-      description = "Nix top-level packages to be compiled using CCache";
+      description = lib.mdDoc "Nix top-level packages to be compiled using CCache";
       default = [];
       example = [ "wxGTK30" "ffmpeg" "libav_all" ];
     };
diff --git a/nixos/modules/programs/cdemu.nix b/nixos/modules/programs/cdemu.nix
index 142e29342405..d43f009f2f92 100644
--- a/nixos/modules/programs/cdemu.nix
+++ b/nixos/modules/programs/cdemu.nix
@@ -10,29 +10,29 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          <command>cdemu</command> for members of
-          <option>programs.cdemu.group</option>.
+        description = lib.mdDoc ''
+          {command}`cdemu` for members of
+          {option}`programs.cdemu.group`.
         '';
       };
       group = mkOption {
         type = types.str;
         default = "cdrom";
-        description = ''
-          Group that users must be in to use <command>cdemu</command>.
+        description = lib.mdDoc ''
+          Group that users must be in to use {command}`cdemu`.
         '';
       };
       gui = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          Whether to install the <command>cdemu</command> GUI (gCDEmu).
+        description = lib.mdDoc ''
+          Whether to install the {command}`cdemu` GUI (gCDEmu).
         '';
       };
       image-analyzer = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install the image analyzer.
         '';
       };
diff --git a/nixos/modules/programs/cfs-zen-tweaks.nix b/nixos/modules/programs/cfs-zen-tweaks.nix
new file mode 100644
index 000000000000..97c2570475c4
--- /dev/null
+++ b/nixos/modules/programs/cfs-zen-tweaks.nix
@@ -0,0 +1,28 @@
+# CFS Zen Tweaks
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs.cfs-zen-tweaks;
+
+in
+
+{
+
+  meta = {
+    maintainers = with maintainers; [ mkg20001 ];
+  };
+
+  options = {
+    programs.cfs-zen-tweaks.enable = mkEnableOption (lib.mdDoc "CFS Zen Tweaks");
+  };
+
+  config = mkIf cfg.enable {
+    systemd.packages = [ pkgs.cfs-zen-tweaks ];
+
+    systemd.services.set-cfs-tweak.wantedBy = [ "multi-user.target" "suspend.target" "hibernate.target" "hybrid-sleep.target" "suspend-then-hibernate.target" ];
+  };
+}
diff --git a/nixos/modules/programs/chromium.nix b/nixos/modules/programs/chromium.nix
index 4b8bec33eb87..4024f337dfcd 100644
--- a/nixos/modules/programs/chromium.nix
+++ b/nixos/modules/programs/chromium.nix
@@ -19,18 +19,18 @@ in
 
   options = {
     programs.chromium = {
-      enable = mkEnableOption "<command>chromium</command> policies";
+      enable = mkEnableOption (lib.mdDoc "{command}`chromium` policies");
 
       extensions = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of chromium extensions to install.
           For list of plugins ids see id in url of extensions on
-          <link xlink:href="https://chrome.google.com/webstore/category/extensions">chrome web store</link>
+          [chrome web store](https://chrome.google.com/webstore/category/extensions)
           page. To install a chromium extension not included in the chrome web
           store, append to the extension id a semicolon ";" followed by a URL
           pointing to an Update Manifest XML file. See
-          <link xlink:href="https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExtensionInstallForcelist">ExtensionInstallForcelist</link>
+          [ExtensionInstallForcelist](https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExtensionInstallForcelist)
           for additional details.
         '';
         default = [];
@@ -46,21 +46,21 @@ in
 
       homepageLocation = mkOption {
         type = types.nullOr types.str;
-        description = "Chromium default homepage";
+        description = lib.mdDoc "Chromium default homepage";
         default = null;
         example = "https://nixos.org";
       };
 
       defaultSearchProviderEnabled = mkOption {
         type = types.nullOr types.bool;
-        description = "Enable the default search provider.";
+        description = lib.mdDoc "Enable the default search provider.";
         default = null;
         example = true;
       };
 
       defaultSearchProviderSearchURL = mkOption {
         type = types.nullOr types.str;
-        description = "Chromium default search provider url.";
+        description = lib.mdDoc "Chromium default search provider url.";
         default = null;
         example =
           "https://encrypted.google.com/search?q={searchTerms}&{google:RLZ}{google:originalQueryForSuggestion}{google:assistedQueryStats}{google:searchFieldtrialParameter}{google:searchClient}{google:sourceId}{google:instantExtendedEnabledParameter}ie={inputEncoding}";
@@ -68,7 +68,7 @@ in
 
       defaultSearchProviderSuggestURL = mkOption {
         type = types.nullOr types.str;
-        description = "Chromium default search provider url for suggestions.";
+        description = lib.mdDoc "Chromium default search provider url for suggestions.";
         default = null;
         example =
           "https://encrypted.google.com/complete/search?output=chrome&q={searchTerms}";
@@ -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/nixos/modules/programs/clickshare.nix b/nixos/modules/programs/clickshare.nix
deleted file mode 100644
index 9980a7daf525..000000000000
--- a/nixos/modules/programs/clickshare.nix
+++ /dev/null
@@ -1,21 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-{
-
-  options.programs.clickshare-csc1.enable =
-    lib.options.mkEnableOption ''
-      Barco ClickShare CSC-1 driver/client.
-      This allows users in the <literal>clickshare</literal>
-      group to access and use a ClickShare USB dongle
-      that is connected to the machine
-    '';
-
-  config = lib.modules.mkIf config.programs.clickshare-csc1.enable {
-    environment.systemPackages = [ pkgs.clickshare-csc1 ];
-    services.udev.packages = [ pkgs.clickshare-csc1 ];
-    users.groups.clickshare = {};
-  };
-
-  meta.maintainers = [ lib.maintainers.yarny ];
-
-}
diff --git a/nixos/modules/programs/cnping.nix b/nixos/modules/programs/cnping.nix
index d208d2b07040..d3cf659d4297 100644
--- a/nixos/modules/programs/cnping.nix
+++ b/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/nixos/modules/programs/command-not-found/command-not-found.nix b/nixos/modules/programs/command-not-found/command-not-found.nix
index 4d2a89b51584..b5c7626bd207 100644
--- a/nixos/modules/programs/command-not-found/command-not-found.nix
+++ b/nixos/modules/programs/command-not-found/command-not-found.nix
@@ -26,7 +26,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether interactive shells should show which Nix package (if
         any) provides a missing command.
       '';
@@ -34,7 +34,7 @@ in
 
     dbPath = mkOption {
       default = "/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite" ;
-      description = ''
+      description = lib.mdDoc ''
         Absolute path to programs.sqlite.
 
         By default this file will be provided by your channel
diff --git a/nixos/modules/programs/criu.nix b/nixos/modules/programs/criu.nix
index 1714e1331a48..9f03b0c6431a 100644
--- a/nixos/modules/programs/criu.nix
+++ b/nixos/modules/programs/criu.nix
@@ -10,8 +10,8 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Install <command>criu</command> along with necessary kernel options.
+        description = lib.mdDoc ''
+          Install {command}`criu` along with necessary kernel options.
         '';
       };
     };
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index 265c41cbbbc9..7261a143528f 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -28,19 +28,19 @@ in
 
   options = {
     programs.dconf = {
-      enable = mkEnableOption "dconf";
+      enable = mkEnableOption (lib.mdDoc "dconf");
 
       profiles = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = "Set of dconf profile files, installed at <filename>/etc/dconf/profiles/<replaceable>name</replaceable></filename>.";
+        description = lib.mdDoc "Set of dconf profile files, installed at {file}`/etc/dconf/profiles/«name»`.";
         internal = true;
       };
 
       packages = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = "A list of packages which provide dconf profiles and databases in <filename>/etc/dconf</filename>.";
+        description = lib.mdDoc "A list of packages which provide dconf profiles and databases in {file}`/etc/dconf`.";
       };
     };
   };
diff --git a/nixos/modules/programs/digitalbitbox/default.nix b/nixos/modules/programs/digitalbitbox/default.nix
index cabdf260cda3..101ee8ddbafc 100644
--- a/nixos/modules/programs/digitalbitbox/default.nix
+++ b/nixos/modules/programs/digitalbitbox/default.nix
@@ -11,7 +11,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Installs the Digital Bitbox application and enables the complementary hardware module.
       '';
     };
@@ -20,7 +20,7 @@ in
       type = types.package;
       default = pkgs.digitalbitbox;
       defaultText = literalExpression "pkgs.digitalbitbox";
-      description = "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
+      description = lib.mdDoc "The Digital Bitbox package to use. This can be used to install a package with udev rules that differ from the defaults.";
     };
   };
 
diff --git a/nixos/modules/programs/dmrconfig.nix b/nixos/modules/programs/dmrconfig.nix
index d2a5117c48ef..20a0dc9556da 100644
--- a/nixos/modules/programs/dmrconfig.nix
+++ b/nixos/modules/programs/dmrconfig.nix
@@ -6,7 +6,7 @@ let
   cfg = config.programs.dmrconfig;
 
 in {
-  meta.maintainers = [ maintainers.etu ];
+  meta.maintainers = with maintainers; [ ];
 
   ###### interface
   options = {
@@ -14,7 +14,7 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure system to enable use of dmrconfig. This
           enables the required udev rules and installs the program.
         '';
@@ -25,7 +25,7 @@ in {
         default = pkgs.dmrconfig;
         type = types.package;
         defaultText = literalExpression "pkgs.dmrconfig";
-        description = "dmrconfig derivation to use";
+        description = lib.mdDoc "dmrconfig derivation to use";
       };
     };
   };
diff --git a/nixos/modules/programs/droidcam.nix b/nixos/modules/programs/droidcam.nix
index 9843a1f5be25..c9b4457d1d18 100644
--- a/nixos/modules/programs/droidcam.nix
+++ b/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/nixos/modules/programs/evince.nix b/nixos/modules/programs/evince.nix
index c033230afb10..9ed5ea0feb04 100644
--- a/nixos/modules/programs/evince.nix
+++ b/nixos/modules/programs/evince.nix
@@ -22,13 +22,13 @@ in {
     programs.evince = {
 
       enable = mkEnableOption
-        "Evince, the GNOME document viewer";
+        (lib.mdDoc "Evince, the GNOME document viewer");
 
       package = mkOption {
         type = types.package;
         default = pkgs.evince;
         defaultText = literalExpression "pkgs.evince";
-        description = "Evince derivation to use.";
+        description = lib.mdDoc "Evince derivation to use.";
       };
 
     };
diff --git a/nixos/modules/programs/extra-container.nix b/nixos/modules/programs/extra-container.nix
index c10ccd769168..5e717c4d8223 100644
--- a/nixos/modules/programs/extra-container.nix
+++ b/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/nixos/modules/programs/feedbackd.nix b/nixos/modules/programs/feedbackd.nix
index 4194080c8a73..cee8daa31462 100644
--- a/nixos/modules/programs/feedbackd.nix
+++ b/nixos/modules/programs/feedbackd.nix
@@ -7,13 +7,13 @@ 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 = ''
+        description = lib.mdDoc ''
           Which feedbackd package to use.
         '';
         type = types.package;
diff --git a/nixos/modules/programs/file-roller.nix b/nixos/modules/programs/file-roller.nix
index 3c47d5981654..ca0c4d1b2a2a 100644
--- a/nixos/modules/programs/file-roller.nix
+++ b/nixos/modules/programs/file-roller.nix
@@ -21,13 +21,13 @@ 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;
         default = pkgs.gnome.file-roller;
         defaultText = literalExpression "pkgs.gnome.file-roller";
-        description = "File Roller derivation to use.";
+        description = lib.mdDoc "File Roller derivation to use.";
       };
 
     };
diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix
new file mode 100644
index 000000000000..3a5105c57d76
--- /dev/null
+++ b/nixos/modules/programs/firefox.nix
@@ -0,0 +1,264 @@
+{ 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";
+      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.gsconnect [
+          gnomeExtensions.gsconnect
+        ] ++ optionals nmh.jabref [
+          jabref
+        ] ++ optionals nmh.passff [
+          passff-host
+        ];
+      })
+    ];
+
+    nixpkgs.config.firefox = {
+      enableBrowserpass = nmh.browserpass;
+      enableBukubrow = nmh.bukubrow;
+      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/nixos/modules/programs/firejail.nix b/nixos/modules/programs/firejail.nix
index 76b42168c198..6f79c13d94b4 100644
--- a/nixos/modules/programs/firejail.nix
+++ b/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" ];
           };
         };
@@ -69,15 +83,8 @@ in {
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Wrap the binaries in firejail and place them in the global path.
-        </para>
-        <para>
-        You will get file collisions if you put the actual application binary in
-        the global environment (such as by adding the application package to
-        <code>environment.systemPackages</code>), and applications started via
-        .desktop files are not wrapped if they specify the absolute path to the
-        binary.
       '';
     };
   };
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 8dd7101947fa..160adc0cad6d 100644
--- a/nixos/modules/programs/fish.nix
+++ b/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;";
 
@@ -49,7 +49,7 @@ in
 
       enable = mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure fish as an interactive shell.
         '';
         type = types.bool;
@@ -58,16 +58,16 @@ in
       useBabelfish = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          If enabled, the configured environment will be translated to native fish using <link xlink:href="https://github.com/bouk/babelfish">babelfish</link>.
-          Otherwise, <link xlink:href="https://github.com/oh-my-fish/plugin-foreign-env">foreign-env</link> will be used.
+        description = lib.mdDoc ''
+          If enabled, the configured environment will be translated to native fish using [babelfish](https://github.com/bouk/babelfish).
+          Otherwise, [foreign-env](https://github.com/oh-my-fish/plugin-foreign-env) will be used.
         '';
       };
 
       vendor.config.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether fish should source configuration snippets provided by other packages.
         '';
       };
@@ -75,7 +75,7 @@ in
       vendor.completions.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether fish should use completion files provided by other packages.
         '';
       };
@@ -83,7 +83,7 @@ in
       vendor.functions.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether fish should autoload fish functions provided by other packages.
         '';
       };
@@ -94,7 +94,7 @@ in
           gco = "git checkout";
           npu = "nix-prefetch-url";
         };
-        description = ''
+        description = lib.mdDoc ''
           Set of fish abbreviations.
         '';
         type = with types; attrsOf str;
@@ -102,16 +102,16 @@ in
 
       shellAliases = mkOption {
         default = {};
-        description = ''
-          Set of aliases for fish shell, which overrides <option>environment.shellAliases</option>.
-          See <option>environment.shellAliases</option> for an option format description.
+        description = lib.mdDoc ''
+          Set of aliases for fish shell, which overrides {option}`environment.shellAliases`.
+          See {option}`environment.shellAliases` for an option format description.
         '';
         type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during fish shell initialisation.
         '';
         type = types.lines;
@@ -119,7 +119,7 @@ in
 
       loginShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during fish login shell initialisation.
         '';
         type = types.lines;
@@ -127,7 +127,7 @@ in
 
       interactiveShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during interactive fish shell initialisation.
         '';
         type = types.lines;
@@ -135,7 +135,7 @@ in
 
       promptInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code used to initialise fish prompt.
         '';
         type = types.lines;
diff --git a/nixos/modules/programs/flashrom.nix b/nixos/modules/programs/flashrom.nix
index f026c2e31cda..ff495558c9e0 100644
--- a/nixos/modules/programs/flashrom.nix
+++ b/nixos/modules/programs/flashrom.nix
@@ -10,17 +10,18 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Installs flashrom and configures udev rules for programmers
         used by flashrom. Grants access to users in the "flashrom"
         group.
       '';
     };
+    package = mkPackageOption pkgs "flashrom" { };
   };
 
   config = mkIf cfg.enable {
-    services.udev.packages = [ pkgs.flashrom ];
-    environment.systemPackages = [ pkgs.flashrom ];
+    services.udev.packages = [ cfg.package ];
+    environment.systemPackages = [ cfg.package ];
     users.groups.flashrom = { };
   };
 }
diff --git a/nixos/modules/programs/flexoptix-app.nix b/nixos/modules/programs/flexoptix-app.nix
index 5e169be2d893..2524e7ba4d58 100644
--- a/nixos/modules/programs/flexoptix-app.nix
+++ b/nixos/modules/programs/flexoptix-app.nix
@@ -7,10 +7,10 @@ let
 in {
   options = {
     programs.flexoptix-app = {
-      enable = mkEnableOption "FLEXOPTIX app + udev rules";
+      enable = mkEnableOption (lib.mdDoc "FLEXOPTIX app + udev rules");
 
       package = mkOption {
-        description = "FLEXOPTIX app package to use";
+        description = lib.mdDoc "FLEXOPTIX app package to use";
         type = types.package;
         default = pkgs.flexoptix-app;
         defaultText = literalExpression "pkgs.flexoptix-app";
diff --git a/nixos/modules/programs/freetds.nix b/nixos/modules/programs/freetds.nix
index d95c44d756af..98274fa9b562 100644
--- a/nixos/modules/programs/freetds.nix
+++ b/nixos/modules/programs/freetds.nix
@@ -26,7 +26,7 @@ in
         }
       '';
       description =
-        ''
+        lib.mdDoc ''
         Configure freetds database entries. Each attribute denotes
         a section within freetds.conf, and the value (a string) is the config
         content for that section. When at least one entry is configured
diff --git a/nixos/modules/programs/fuse.nix b/nixos/modules/programs/fuse.nix
index c15896efbb51..b82d37a051e7 100644
--- a/nixos/modules/programs/fuse.nix
+++ b/nixos/modules/programs/fuse.nix
@@ -13,7 +13,7 @@ in {
       # negative numbers obviously make no sense:
       type = types.ints.between 0 32767; # 2^15 - 1
       default = 1000;
-      description = ''
+      description = lib.mdDoc ''
         Set the maximum number of FUSE mounts allowed to non-root users.
       '';
     };
@@ -21,7 +21,7 @@ in {
     userAllowOther = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Allow non-root users to specify the allow_other or allow_root mount
         options, see mount.fuse3(8).
       '';
diff --git a/nixos/modules/programs/fzf.nix b/nixos/modules/programs/fzf.nix
new file mode 100644
index 000000000000..eda4eacde4ac
--- /dev/null
+++ b/nixos/modules/programs/fzf.nix
@@ -0,0 +1,27 @@
+{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 cfg.fuzzyCompletion ''
+      source ${pkgs.fzf}/share/fzf/completion.zsh
+    '' + optionalString cfg.keybindings ''
+      source ${pkgs.fzf}/share/fzf/key-bindings.zsh
+    '';
+  };
+  meta.maintainers = with maintainers; [ laalsaas ];
+}
diff --git a/nixos/modules/programs/gamemode.nix b/nixos/modules/programs/gamemode.nix
index a377a1619aa0..c43e2c2296f5 100644
--- a/nixos/modules/programs/gamemode.nix
+++ b/nixos/modules/programs/gamemode.nix
@@ -10,16 +10,16 @@ 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;
       };
 
       settings = mkOption {
         type = settingsFormat.type;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           System-wide configuration for GameMode (/etc/gamemode.ini).
           See gamemoded(8) man page for available settings.
         '';
diff --git a/nixos/modules/programs/geary.nix b/nixos/modules/programs/geary.nix
index 407680c30dc3..d9454a2247fd 100644
--- a/nixos/modules/programs/geary.nix
+++ b/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/nixos/modules/programs/git.nix b/nixos/modules/programs/git.nix
index 06ce374b1992..4e271a8c134b 100644
--- a/nixos/modules/programs/git.nix
+++ b/nixos/modules/programs/git.nix
@@ -9,37 +9,63 @@ in
 {
   options = {
     programs.git = {
-      enable = mkEnableOption "git";
+      enable = mkEnableOption (lib.mdDoc "git");
 
       package = mkOption {
         type = types.package;
         default = pkgs.git;
         defaultText = literalExpression "pkgs.git";
         example = literalExpression "pkgs.gitFull";
-        description = "The git package to use";
+        description = lib.mdDoc "The git package to use";
       };
 
       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 = ''
-          Configuration to write to /etc/gitconfig. See the CONFIGURATION FILE
-          section of git-config(1) for more information.
+        description = lib.mdDoc ''
+          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;
           default = pkgs.git-lfs;
           defaultText = literalExpression "pkgs.git-lfs";
-          description = "The git-lfs package to use";
+          description = lib.mdDoc "The git-lfs package to use";
         };
       };
     };
@@ -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/nixos/modules/programs/gnome-disks.nix b/nixos/modules/programs/gnome-disks.nix
index 4b128b471265..dcb20bd6037c 100644
--- a/nixos/modules/programs/gnome-disks.nix
+++ b/nixos/modules/programs/gnome-disks.nix
@@ -26,7 +26,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GNOME Disks daemon, a program designed to
           be a UDisks2 graphical front-end.
         '';
diff --git a/nixos/modules/programs/gnome-documents.nix b/nixos/modules/programs/gnome-documents.nix
index 43ad3163efd8..2831ac9aff2e 100644
--- a/nixos/modules/programs/gnome-documents.nix
+++ b/nixos/modules/programs/gnome-documents.nix
@@ -26,7 +26,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GNOME Documents, a document
           manager application for GNOME.
         '';
diff --git a/nixos/modules/programs/gnome-terminal.nix b/nixos/modules/programs/gnome-terminal.nix
index 71a6b217880c..a8d82e0b018c 100644
--- a/nixos/modules/programs/gnome-terminal.nix
+++ b/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/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index b41f30287ea5..828f24f99111 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -17,7 +17,7 @@ let
     else if xserverCfg.enable || config.programs.sway.enable then
       "gnome3"
     else
-      null;
+      "curses";
 
 in
 
@@ -28,7 +28,7 @@ in
       type = types.package;
       default = pkgs.gnupg;
       defaultText = literalExpression "pkgs.gnupg";
-      description = ''
+      description = lib.mdDoc ''
         The gpg package that should be used.
       '';
     };
@@ -36,7 +36,7 @@ in
     agent.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables GnuPG agent with socket-activation for every user session.
       '';
     };
@@ -44,7 +44,7 @@ in
     agent.enableSSHSupport = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable SSH agent support in GnuPG agent. Also sets SSH_AUTH_SOCK
         environment variable correctly. This will disable socket-activation
         and thus always start a GnuPG agent per user session.
@@ -54,7 +54,7 @@ in
     agent.enableExtraSocket = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable extra socket for GnuPG agent.
       '';
     };
@@ -62,7 +62,7 @@ in
     agent.enableBrowserSocket = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable browser socket for GnuPG agent.
       '';
     };
@@ -71,8 +71,8 @@ in
       type = types.nullOr (types.enum pkgs.pinentry.flavors);
       example = "gnome3";
       default = defaultPinentryFlavor;
-      defaultText = literalDocBook ''matching the configured desktop environment'';
-      description = ''
+      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
         thus overrides the pinentry option in gpg-agent.conf in the user's
@@ -86,7 +86,7 @@ in
     dirmngr.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables GnuPG network certificate management daemon with socket-activation for every user session.
       '';
     };
@@ -129,12 +129,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 "${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/nixos/modules/programs/gpaste.nix b/nixos/modules/programs/gpaste.nix
index cff2fb8d0034..074b4d59a365 100644
--- a/nixos/modules/programs/gpaste.nix
+++ b/nixos/modules/programs/gpaste.nix
@@ -18,7 +18,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GPaste, a clipboard manager.
         '';
       };
diff --git a/nixos/modules/programs/gphoto2.nix b/nixos/modules/programs/gphoto2.nix
index 93923ff3133c..f31b1863963d 100644
--- a/nixos/modules/programs/gphoto2.nix
+++ b/nixos/modules/programs/gphoto2.nix
@@ -11,11 +11,11 @@ with lib;
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure system to use gphoto2.
           To grant digital camera access to a user, the user must
           be part of the camera group:
-          <code>users.users.alice.extraGroups = ["camera"];</code>
+          `users.users.alice.extraGroups = ["camera"];`
         '';
       };
     };
diff --git a/nixos/modules/programs/haguichi.nix b/nixos/modules/programs/haguichi.nix
new file mode 100644
index 000000000000..699327c28c61
--- /dev/null
+++ b/nixos/modules/programs/haguichi.nix
@@ -0,0 +1,15 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+{
+  options.programs.haguichi = {
+    enable = mkEnableOption (lib.mdDoc "Haguichi, a Linux GUI frontend to the proprietary LogMeIn Hamachi");
+  };
+
+  config = mkIf config.programs.haguichi.enable {
+    environment.systemPackages = with pkgs; [ haguichi ];
+
+    services.logmein-hamachi.enable = true;
+  };
+}
diff --git a/nixos/modules/programs/hamster.nix b/nixos/modules/programs/hamster.nix
index 0bb56ad7ff36..f50438cc1704 100644
--- a/nixos/modules/programs/hamster.nix
+++ b/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/nixos/modules/programs/htop.nix b/nixos/modules/programs/htop.nix
index 5c197838e47c..2682ced490ca 100644
--- a/nixos/modules/programs/htop.nix
+++ b/nixos/modules/programs/htop.nix
@@ -20,13 +20,13 @@ in
     package = mkOption {
       type = types.package;
       default = pkgs.htop;
-      defaultText = "pkgs.htop";
-      description = ''
+      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 ])) ]);
@@ -35,7 +35,7 @@ in
         hide_kernel_threads = true;
         hide_userland_threads = true;
       };
-      description = ''
+      description = lib.mdDoc ''
         Extra global default configuration for htop
         which is read on first startup only.
         Htop subsequently uses ~/.config/htop/htoprc
diff --git a/nixos/modules/programs/i3lock.nix b/nixos/modules/programs/i3lock.nix
new file mode 100644
index 000000000000..466ae59c9277
--- /dev/null
+++ b/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/nixos/modules/programs/iftop.nix b/nixos/modules/programs/iftop.nix
index c74714a9a6d6..1db018858b65 100644
--- a/nixos/modules/programs/iftop.nix
+++ b/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/nixos/modules/programs/iotop.nix b/nixos/modules/programs/iotop.nix
index b7c1c69f9ddd..0eb60b989eb3 100644
--- a/nixos/modules/programs/iotop.nix
+++ b/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/nixos/modules/programs/java.nix b/nixos/modules/programs/java.nix
index 4e4e0629e5d9..4f03c1f3ff25 100644
--- a/nixos/modules/programs/java.nix
+++ b/nixos/modules/programs/java.nix
@@ -15,27 +15,26 @@ in
 
     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 ];
+          :::
         '';
       };
 
       package = mkOption {
         default = pkgs.jdk;
         defaultText = literalExpression "pkgs.jdk";
-        description = ''
+        description = lib.mdDoc ''
           Java package to install. Typical values are pkgs.jdk or pkgs.jre.
         '';
         type = types.package;
diff --git a/nixos/modules/programs/k3b.nix b/nixos/modules/programs/k3b.nix
new file mode 100644
index 000000000000..cdaed3cf70fb
--- /dev/null
+++ b/nixos/modules/programs/k3b.nix
@@ -0,0 +1,52 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  # interface
+  options.programs.k3b = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to enable k3b, the KDE disk burning application.
+
+        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.
+      '';
+    };
+  };
+
+  # implementation
+  config = mkIf config.programs.k3b.enable {
+
+    environment.systemPackages = with pkgs; [
+      k3b
+      dvdplusrwtools
+      cdrdao
+      cdrkit
+    ];
+
+    security.wrappers = {
+      cdrdao = {
+        setuid = true;
+        owner = "root";
+        group = "cdrom";
+        permissions = "u+wrx,g+x";
+        source = "${pkgs.cdrdao}/bin/cdrdao";
+      };
+      cdrecord = {
+        setuid = true;
+        owner = "root";
+        group = "cdrom";
+        permissions = "u+wrx,g+x";
+        source = "${pkgs.cdrkit}/bin/cdrecord";
+      };
+    };
+
+  };
+}
diff --git a/nixos/modules/programs/k40-whisperer.nix b/nixos/modules/programs/k40-whisperer.nix
index 3163e45f57e4..27a79caa4b53 100644
--- a/nixos/modules/programs/k40-whisperer.nix
+++ b/nixos/modules/programs/k40-whisperer.nix
@@ -10,11 +10,11 @@ let
 in
 {
   options.programs.k40-whisperer = {
-    enable = mkEnableOption "K40-Whisperer";
+    enable = mkEnableOption (lib.mdDoc "K40-Whisperer");
 
     group = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Group assigned to the device when connected.
       '';
       default = "k40";
@@ -25,7 +25,7 @@ in
       default = pkgs.k40-whisperer;
       defaultText = literalExpression "pkgs.k40-whisperer";
       example = literalExpression "pkgs.k40-whisperer";
-      description = ''
+      description = lib.mdDoc ''
         K40 Whisperer package to use.
       '';
     };
diff --git a/nixos/modules/programs/kbdlight.nix b/nixos/modules/programs/kbdlight.nix
index 8a2a0057cf2d..6c3c79ddb4aa 100644
--- a/nixos/modules/programs/kbdlight.nix
+++ b/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/nixos/modules/programs/kclock.nix b/nixos/modules/programs/kclock.nix
index 42d81d2798ba..63d6fb1e2d7f 100644
--- a/nixos/modules/programs/kclock.nix
+++ b/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/nixos/modules/programs/kdeconnect.nix b/nixos/modules/programs/kdeconnect.nix
index df698e84dd70..4978c428ce34 100644
--- a/nixos/modules/programs/kdeconnect.nix
+++ b/nixos/modules/programs/kdeconnect.nix
@@ -2,21 +2,21 @@
 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
-      <code>gnomeExtensions.gsconnect</code> 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.kdeconnect;
-      defaultText = literalExpression "pkgs.kdeconnect";
+      default = pkgs.plasma5Packages.kdeconnect-kde;
+      defaultText = literalExpression "pkgs.plasma5Packages.kdeconnect-kde";
       type = types.package;
       example = literalExpression "pkgs.gnomeExtensions.gsconnect";
-      description = ''
+      description = lib.mdDoc ''
         The package providing the implementation for kdeconnect.
       '';
     };
diff --git a/nixos/modules/programs/less.nix b/nixos/modules/programs/less.nix
index 794146b19faf..a1134e774364 100644
--- a/nixos/modules/programs/less.nix
+++ b/nixos/modules/programs/less.nix
@@ -35,18 +35,18 @@ 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;
         default = null;
         example = literalExpression ''"''${pkgs.my-configs}/lesskey"'';
-        description = ''
+        description = lib.mdDoc ''
           Path to lesskey configuration file.
 
-          <option>configFile</option> takes precedence over <option>commands</option>,
-          <option>clearDefaultCommands</option>, <option>lineEditingKeys</option>, and
-          <option>envVariables</option>.
+          {option}`configFile` takes precedence over {option}`commands`,
+          {option}`clearDefaultCommands`, {option}`lineEditingKeys`, and
+          {option}`envVariables`.
         '';
       };
 
@@ -57,13 +57,13 @@ in
           h = "noaction 5\\e(";
           l = "noaction 5\\e)";
         };
-        description = "Defines new command keys.";
+        description = lib.mdDoc "Defines new command keys.";
       };
 
       clearDefaultCommands = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Clear all default commands.
           You should remember to set the quit key.
           Otherwise you will not be able to leave less without killing it.
@@ -76,7 +76,7 @@ in
         example = {
           e = "abort";
         };
-        description = "Defines new line-editing keys.";
+        description = lib.mdDoc "Defines new line-editing keys.";
       };
 
       envVariables = mkOption {
@@ -87,14 +87,14 @@ in
         example = {
           LESS = "--quit-if-one-screen";
         };
-        description = "Defines environment variables.";
+        description = lib.mdDoc "Defines environment variables.";
       };
 
       lessopen = mkOption {
         type = types.nullOr types.str;
         default = "|${pkgs.lesspipe}/bin/lesspipe.sh %s";
         defaultText = literalExpression ''"|''${pkgs.lesspipe}/bin/lesspipe.sh %s"'';
-        description = ''
+        description = lib.mdDoc ''
           Before less opens a file, it first gives your input preprocessor a chance to modify the way the contents of the file are displayed.
         '';
       };
@@ -102,8 +102,9 @@ in
       lessclose = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
-          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).
+        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).
         '';
       };
     };
diff --git a/nixos/modules/programs/liboping.nix b/nixos/modules/programs/liboping.nix
index 4433f9767d6e..39e75ba90c9d 100644
--- a/nixos/modules/programs/liboping.nix
+++ b/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/nixos/modules/programs/light.nix b/nixos/modules/programs/light.nix
index 9f2a03e7e763..57cc925be465 100644
--- a/nixos/modules/programs/light.nix
+++ b/nixos/modules/programs/light.nix
@@ -12,7 +12,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install Light backlight control command
           and udev rules granting access to members of the "video" group.
         '';
diff --git a/nixos/modules/programs/mdevctl.nix b/nixos/modules/programs/mdevctl.nix
new file mode 100644
index 000000000000..2b7285233350
--- /dev/null
+++ b/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/nixos/modules/programs/mepo.nix b/nixos/modules/programs/mepo.nix
new file mode 100644
index 000000000000..4b1706a2a0e5
--- /dev/null
+++ b/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/nixos/modules/programs/mininet.nix b/nixos/modules/programs/mininet.nix
index 2cf6c014c352..02272729d233 100644
--- a/nixos/modules/programs/mininet.nix
+++ b/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/nixos/modules/programs/mosh.nix b/nixos/modules/programs/mosh.nix
index e08099e21a00..9e56e1731d7c 100644
--- a/nixos/modules/programs/mosh.nix
+++ b/nixos/modules/programs/mosh.nix
@@ -10,14 +10,14 @@ in
 {
   options.programs.mosh = {
     enable = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable mosh. Note, this will open ports in your firewall!
       '';
       default = false;
       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/nixos/modules/programs/msmtp.nix b/nixos/modules/programs/msmtp.nix
index 9c067bdc9695..a9aed027bdb7 100644
--- a/nixos/modules/programs/msmtp.nix
+++ b/nixos/modules/programs/msmtp.nix
@@ -10,12 +10,12 @@ in {
 
   options = {
     programs.msmtp = {
-      enable = mkEnableOption "msmtp - an SMTP client";
+      enable = mkEnableOption (lib.mdDoc "msmtp - an SMTP client");
 
       setSendmail = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to set the system sendmail to msmtp's.
         '';
       };
@@ -28,7 +28,7 @@ in {
           port = 587;
           tls = true;
         };
-        description = ''
+        description = lib.mdDoc ''
           Default values applied to all accounts.
           See msmtp(1) for the available options.
         '';
@@ -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.
@@ -62,7 +62,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to add to the msmtp configuration verbatim.
           See msmtp(1) for the syntax and available options.
         '';
diff --git a/nixos/modules/programs/mtr.nix b/nixos/modules/programs/mtr.nix
index 3cffe0fd8b2f..173f24729417 100644
--- a/nixos/modules/programs/mtr.nix
+++ b/nixos/modules/programs/mtr.nix
@@ -11,7 +11,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add mtr to the global environment and configure a
           setcap wrapper for it.
         '';
@@ -21,7 +21,7 @@ in {
         type = types.package;
         default = pkgs.mtr;
         defaultText = literalExpression "pkgs.mtr";
-        description = ''
+        description = lib.mdDoc ''
           The package to use.
         '';
       };
diff --git a/nixos/modules/programs/nano.nix b/nixos/modules/programs/nano.nix
index 5837dd46d7cd..16bab620d6e2 100644
--- a/nixos/modules/programs/nano.nix
+++ b/nixos/modules/programs/nano.nix
@@ -14,9 +14,9 @@ in
       nanorc = lib.mkOption {
         type = lib.types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           The system-wide nano configuration.
-          See <citerefentry><refentrytitle>nanorc</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+          See {manpage}`nanorc(5)`.
         '';
         example = ''
           set nowrap
@@ -27,7 +27,7 @@ in
       syntaxHighlight = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = "Whether to enable syntax highlight for various languages.";
+        description = lib.mdDoc "Whether to enable syntax highlight for various languages.";
       };
     };
   };
diff --git a/nixos/modules/programs/nbd.nix b/nixos/modules/programs/nbd.nix
index fea9bc1ff71a..a44403021e35 100644
--- a/nixos/modules/programs/nbd.nix
+++ b/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/nixos/modules/programs/neovim.nix b/nixos/modules/programs/neovim.nix
index 4649662542de..8de527fceb26 100644
--- a/nixos/modules/programs/neovim.nix
+++ b/nixos/modules/programs/neovim.nix
@@ -11,12 +11,24 @@ let
 
 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;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         When enabled, installs neovim and configures neovim to be the default editor
         using the EDITOR environment variable.
       '';
@@ -25,35 +37,35 @@ in {
     viAlias = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Symlink <command>vi</command> to <command>nvim</command> binary.
+      description = lib.mdDoc ''
+        Symlink {command}`vi` to {command}`nvim` binary.
       '';
     };
 
     vimAlias = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Symlink <command>vim</command> to <command>nvim</command> binary.
+      description = lib.mdDoc ''
+        Symlink {command}`vim` to {command}`nvim` binary.
       '';
     };
 
     withRuby = mkOption {
       type = types.bool;
       default = true;
-      description = "Enable Ruby provider.";
+      description = lib.mdDoc "Enable Ruby provider.";
     };
 
     withPython3 = mkOption {
       type = types.bool;
       default = true;
-      description = "Enable Python 3 provider.";
+      description = lib.mdDoc "Enable Python 3 provider.";
     };
 
     withNodeJs = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable Node provider.";
+      description = lib.mdDoc "Enable Node provider.";
     };
 
     configure = mkOption {
@@ -72,9 +84,9 @@ in {
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Generate your init file from your list of plugins and custom commands.
-        Neovim will then be wrapped to load <command>nvim -u /nix/store/<replaceable>hash</replaceable>-vimrc</command>
+        Neovim will then be wrapped to load {command}`nvim -u /nix/store/«hash»-vimrc`
       '';
     };
 
@@ -82,14 +94,14 @@ in {
       type = types.package;
       default = pkgs.neovim-unwrapped;
       defaultText = literalExpression "pkgs.neovim-unwrapped";
-      description = "The package to use for the neovim binary.";
+      description = lib.mdDoc "The package to use for the neovim binary.";
     };
 
     finalPackage = mkOption {
       type = types.package;
       visible = false;
       readOnly = true;
-      description = "Resulting customized neovim package.";
+      description = lib.mdDoc "Resulting customized neovim package.";
     };
 
     runtime = mkOption {
@@ -97,8 +109,8 @@ in {
       example = literalExpression ''
         { "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
       '';
-      description = ''
-        Set of files that have to be linked in <filename>runtime</filename>.
+      description = lib.mdDoc ''
+        Set of files that have to be linked in {file}`runtime`.
       '';
 
       type = with types; attrsOf (submodule (
@@ -108,7 +120,7 @@ in {
             enable = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether this /etc file should be generated.  This
                 option allows specific /etc files to be disabled.
               '';
@@ -116,7 +128,7 @@ in {
 
             target = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Name of symlink.  Defaults to the attribute
                 name.
               '';
@@ -125,12 +137,12 @@ in {
             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/nixos/modules/programs/nethoscope.nix b/nixos/modules/programs/nethoscope.nix
index 495548e9c656..d8ece61c90a2 100644
--- a/nixos/modules/programs/nethoscope.nix
+++ b/nixos/modules/programs/nethoscope.nix
@@ -12,7 +12,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add nethoscope to the global environment and configure a
           setcap wrapper for it.
         '';
diff --git a/nixos/modules/programs/nix-ld.nix b/nixos/modules/programs/nix-ld.nix
index 810a74ab50b7..f753cf5f97e5 100644
--- a/nixos/modules/programs/nix-ld.nix
+++ b/nixos/modules/programs/nix-ld.nix
@@ -1,12 +1,68 @@
 { pkgs, lib, config, ... }:
+let
+  cfg = config.programs.nix-ld;
+
+  # TODO make glibc here configureable?
+  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"/>'';
+    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.mdDoc "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.mdDoc "baseLibraries";
+      };
+    };
   };
   config = lib.mkIf config.programs.nix-ld.enable {
-    systemd.tmpfiles.rules = [
-      "L+ ${pkgs.nix-ld.ldPath} - - - - ${pkgs.nix-ld}/libexec/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/nixos/modules/programs/nm-applet.nix b/nixos/modules/programs/nm-applet.nix
index 5bcee30125bb..4b09b1884d7e 100644
--- a/nixos/modules/programs/nm-applet.nix
+++ b/nixos/modules/programs/nm-applet.nix
@@ -6,12 +6,12 @@
   };
 
   options.programs.nm-applet = {
-    enable = lib.mkEnableOption "nm-applet";
+    enable = lib.mkEnableOption (lib.mdDoc "nm-applet");
 
     indicator = lib.mkOption {
       type = lib.types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to use indicator instead of status icon.
         It is needed for Appindicator environments, like Enlightenment.
       '';
diff --git a/nixos/modules/programs/nncp.nix b/nixos/modules/programs/nncp.nix
index 29a703eadf10..98fea84ab740 100644
--- a/nixos/modules/programs/nncp.nix
+++ b/nixos/modules/programs/nncp.nix
@@ -11,12 +11,12 @@ 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;
       default = "uucp";
-      description = ''
+      description = lib.mdDoc ''
         The group under which NNCP files shall be owned.
         Any member of this group may access the secret keys
         of this NNCP node.
@@ -27,30 +27,30 @@ in {
       type = types.package;
       default = pkgs.nncp;
       defaultText = literalExpression "pkgs.nncp";
-      description = "The NNCP package to use system-wide.";
+      description = lib.mdDoc "The NNCP package to use system-wide.";
     };
 
     secrets = mkOption {
       type = with types; listOf str;
       example = [ "/run/keys/nncp.hjson" ];
-      description = ''
+      description = lib.mdDoc ''
         A list of paths to NNCP configuration files that should not be
         in the Nix store. These files are layered on top of the values at
-        <xref linkend="opt-programs.nncp.settings"/>.
+        [](#opt-programs.nncp.settings).
       '';
     };
 
     settings = mkOption {
       type = settingsFormat.type;
-      description = ''
+      description = lib.mdDoc ''
         NNCP configuration, see
-        <link xlink:href="http://www.nncpgo.org/Configuration.html"/>.
+        <http://www.nncpgo.org/Configuration.html>.
         At runtime these settings will be overlayed by the contents of
-        <xref linkend="opt-programs.nncp.secrets"/> into the file
-        <literal>${nncpCfgFile}</literal>. Node keypairs go in
-        <literal>secrets</literal>, do not specify them in
-        <literal>settings</literal> as they will be leaked into
-        <literal>/nix/store</literal>!
+        [](#opt-programs.nncp.secrets) into the file
+        `${nncpCfgFile}`. Node keypairs go in
+        `secrets`, do not specify them in
+        `settings` as they will be leaked into
+        `/nix/store`!
       '';
       default = { };
     };
diff --git a/nixos/modules/programs/noisetorch.nix b/nixos/modules/programs/noisetorch.nix
index f76555289f1a..c022b01d79af 100644
--- a/nixos/modules/programs/noisetorch.nix
+++ b/nixos/modules/programs/noisetorch.nix
@@ -3,15 +3,16 @@
 with lib;
 
 let cfg = config.programs.noisetorch;
-in {
+in
+{
   options.programs.noisetorch = {
-    enable = mkEnableOption "noisetorch + setcap wrapper";
+    enable = mkEnableOption (lib.mdDoc "noisetorch + setcap wrapper");
 
     package = mkOption {
       type = types.package;
       default = pkgs.noisetorch;
       defaultText = literalExpression "pkgs.noisetorch";
-      description = ''
+      description = lib.mdDoc ''
         The noisetorch package to use.
       '';
     };
@@ -24,5 +25,6 @@ in {
       capabilities = "cap_sys_resource=+ep";
       source = "${cfg.package}/bin/noisetorch";
     };
+    environment.systemPackages = [ cfg.package ];
   };
 }
diff --git a/nixos/modules/programs/npm.nix b/nixos/modules/programs/npm.nix
index d79c6c734000..48dc48e668f3 100644
--- a/nixos/modules/programs/npm.nix
+++ b/nixos/modules/programs/npm.nix
@@ -11,11 +11,11 @@ in
 
   options = {
     programs.npm = {
-      enable = mkEnableOption "<command>npm</command> global config";
+      enable = mkEnableOption (lib.mdDoc "{command}`npm` global config");
 
       package = mkOption {
         type = types.package;
-        description = "The npm package version / flavor to use";
+        description = lib.mdDoc "The npm package version / flavor to use";
         default = pkgs.nodePackages.npm;
         defaultText = literalExpression "pkgs.nodePackages.npm";
         example = literalExpression "pkgs.nodePackages_13_x.npm";
@@ -23,9 +23,9 @@ in
 
       npmrc = mkOption {
         type = lib.types.lines;
-        description = ''
+        description = lib.mdDoc ''
           The system-wide npm configuration.
-          See <link xlink:href="https://docs.npmjs.com/misc/config"/>.
+          See <https://docs.npmjs.com/misc/config>.
         '';
         default = ''
           prefix = ''${HOME}/.npm
diff --git a/nixos/modules/programs/openvpn3.nix b/nixos/modules/programs/openvpn3.nix
new file mode 100644
index 000000000000..df7e9ef22c10
--- /dev/null
+++ b/nixos/modules/programs/openvpn3.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.openvpn3;
+in
+{
+  options.programs.openvpn3 = {
+    enable = mkEnableOption (lib.mdDoc "the openvpn3 client");
+  };
+
+  config = mkIf cfg.enable {
+    services.dbus.packages = with pkgs; [
+      openvpn3
+    ];
+
+    users.users.openvpn = {
+      isSystemUser = true;
+      uid = config.ids.uids.openvpn;
+      group = "openvpn";
+    };
+
+    users.groups.openvpn = {
+      gid = config.ids.gids.openvpn;
+    };
+
+    environment.systemPackages = with pkgs; [
+      openvpn3
+    ];
+  };
+
+}
diff --git a/nixos/modules/programs/pantheon-tweaks.nix b/nixos/modules/programs/pantheon-tweaks.nix
index 0b8a19ea22c0..82f93619db15 100644
--- a/nixos/modules/programs/pantheon-tweaks.nix
+++ b/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/nixos/modules/programs/partition-manager.nix b/nixos/modules/programs/partition-manager.nix
index 1be2f0a69a11..c18598b7c25d 100644
--- a/nixos/modules/programs/partition-manager.nix
+++ b/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/nixos/modules/programs/plotinus.nix b/nixos/modules/programs/plotinus.nix
index 2c90a41ba029..a011bb862aea 100644
--- a/nixos/modules/programs/plotinus.nix
+++ b/nixos/modules/programs/plotinus.nix
@@ -17,7 +17,7 @@ in
     programs.plotinus = {
       enable = mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Plotinus GTK 3 plugin. Plotinus provides a
           popup (triggered by Ctrl-Shift-P) to search the menus of a
           compatible application.
diff --git a/nixos/modules/programs/proxychains.nix b/nixos/modules/programs/proxychains.nix
index 3f44e23a93ef..0771f03c77d3 100644
--- a/nixos/modules/programs/proxychains.nix
+++ b/nixos/modules/programs/proxychains.nix
@@ -22,21 +22,21 @@ let
 
   proxyOptions = {
     options = {
-      enable = mkEnableOption "this proxy";
+      enable = mkEnableOption (lib.mdDoc "this proxy");
 
       type = mkOption {
         type = types.enum [ "http" "socks4" "socks5" ];
-        description = "Proxy type.";
+        description = lib.mdDoc "Proxy type.";
       };
 
       host = mkOption {
         type = types.str;
-        description = "Proxy host or IP address.";
+        description = lib.mdDoc "Proxy host or IP address.";
       };
 
       port = mkOption {
         type = types.port;
-        description = "Proxy port";
+        description = lib.mdDoc "Proxy port";
       };
     };
   };
@@ -49,32 +49,32 @@ in {
 
     programs.proxychains = {
 
-      enable = mkEnableOption "installing proxychains configuration";
+      enable = mkEnableOption (lib.mdDoc "installing proxychains configuration");
 
       chain = {
         type = mkOption {
           type = types.enum [ "dynamic" "strict" "random" ];
           default = "strict";
-          description = ''
-            <literal>dynamic</literal> - Each connection will be done via chained proxies
+          description = lib.mdDoc ''
+            `dynamic` - Each connection will be done via chained proxies
             all proxies chained in the order as they appear in the list
             at least one proxy must be online to play in chain
             (dead proxies are skipped)
-            otherwise <literal>EINTR</literal> is returned to the app.
+            otherwise `EINTR` is returned to the app.
 
-            <literal>strict</literal> - Each connection will be done via chained proxies
+            `strict` - Each connection will be done via chained proxies
             all proxies chained in the order as they appear in the list
             all proxies must be online to play in chain
-            otherwise <literal>EINTR</literal> is returned to the app.
+            otherwise `EINTR` is returned to the app.
 
-            <literal>random</literal> - Each connection will be done via random proxy
-            (or proxy chain, see <option>programs.proxychains.chain.length</option>) from the list.
+            `random` - Each connection will be done via random proxy
+            (or proxy chain, see {option}`programs.proxychains.chain.length`) from the list.
           '';
         };
         length = mkOption {
           type = types.nullOr types.int;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Chain length for random chain.
           '';
         };
@@ -83,15 +83,15 @@ in {
       proxyDNS = mkOption {
         type = types.bool;
         default = true;
-        description = "Proxy DNS requests - no leak for DNS data.";
+        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 ];
         default = 224;
-        description = ''
+        description = lib.mdDoc ''
           Set the class A subnet number to use for the internal remote DNS mapping, uses the reserved 224.x.x.x range by default.
         '';
       };
@@ -99,24 +99,24 @@ in {
       tcpReadTimeOut = mkOption {
         type = types.int;
         default = 15000;
-        description = "Connection read time-out in milliseconds.";
+        description = lib.mdDoc "Connection read time-out in milliseconds.";
       };
 
       tcpConnectTimeOut = mkOption {
         type = types.int;
         default = 8000;
-        description = "Connection time-out in milliseconds.";
+        description = lib.mdDoc "Connection time-out in milliseconds.";
       };
 
       localnet = mkOption {
         type = types.str;
         default = "127.0.0.0/255.0.0.0";
-        description = "By default enable localnet for loopback address ranges.";
+        description = lib.mdDoc "By default enable localnet for loopback address ranges.";
       };
 
       proxies = mkOption {
         type = types.attrsOf (types.submodule proxyOptions);
-        description = ''
+        description = lib.mdDoc ''
           Proxies to be used by proxychains.
         '';
 
diff --git a/nixos/modules/programs/qt5ct.nix b/nixos/modules/programs/qt5ct.nix
index 88e861bf4031..3ff47b355915 100644
--- a/nixos/modules/programs/qt5ct.nix
+++ b/nixos/modules/programs/qt5ct.nix
@@ -1,31 +1,9 @@
-{ config, lib, pkgs, ... }:
+{ lib, ... }:
 
 with lib;
 
 {
-  meta.maintainers = [ maintainers.romildo ];
-
-  ###### interface
-  options = {
-    programs.qt5ct = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Whether to enable the Qt5 Configuration Tool (qt5ct), a
-          program that allows users to configure Qt5 settings (theme,
-          font, icons, etc.) under desktop environments or window
-          manager without Qt integration.
-
-          Official home page: <link xlink:href="https://sourceforge.net/projects/qt5ct/">https://sourceforge.net/projects/qt5ct/</link>
-        '';
-      };
-    };
-  };
-
-  ###### implementation
-  config = mkIf config.programs.qt5ct.enable {
-    environment.variables.QT_QPA_PLATFORMTHEME = "qt5ct";
-    environment.systemPackages = with pkgs; [ libsForQt5.qt5ct ];
-  };
+  imports = [
+    (mkRemovedOptionModule [ "programs" "qt5ct" "enable" ] "Use qt5.platformTheme = \"qt5ct\" instead.")
+  ];
 }
diff --git a/nixos/modules/programs/rog-control-center.nix b/nixos/modules/programs/rog-control-center.nix
new file mode 100644
index 000000000000..4aef5143ac7f
--- /dev/null
+++ b/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/nixos/modules/programs/rust-motd.nix b/nixos/modules/programs/rust-motd.nix
new file mode 100644
index 000000000000..d5f1820ba752
--- /dev/null
+++ b/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/nixos/modules/programs/screen.nix b/nixos/modules/programs/screen.nix
index 728a0eb8cea5..68de9e52d7be 100644
--- a/nixos/modules/programs/screen.nix
+++ b/nixos/modules/programs/screen.nix
@@ -13,7 +13,7 @@ in
 
       screenrc = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           The contents of /etc/screenrc file.
         '';
         type = types.lines;
diff --git a/nixos/modules/programs/seahorse.nix b/nixos/modules/programs/seahorse.nix
index c0a356bff57c..5e179c1446ed 100644
--- a/nixos/modules/programs/seahorse.nix
+++ b/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/nixos/modules/programs/sedutil.nix b/nixos/modules/programs/sedutil.nix
index 7efc80f4abba..d5e20a8815d4 100644
--- a/nixos/modules/programs/sedutil.nix
+++ b/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/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index 963cd8853dbb..fab809f279a3 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -59,7 +59,7 @@ in
   options = {
 
     users.defaultUserShell = lib.mkOption {
-      description = ''
+      description = lib.mdDoc ''
         This option defines the default shell assigned to user
         accounts. This can be either a full system path or a shell package.
 
diff --git a/nixos/modules/programs/singularity.nix b/nixos/modules/programs/singularity.nix
index db935abe4bb4..9648d0c27874 100644
--- a/nixos/modules/programs/singularity.nix
+++ b/nixos/modules/programs/singularity.nix
@@ -11,7 +11,7 @@ let
   });
 in {
   options.programs.singularity = {
-    enable = mkEnableOption "Singularity";
+    enable = mkEnableOption (lib.mdDoc "Singularity");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/programs/skim.nix b/nixos/modules/programs/skim.nix
new file mode 100644
index 000000000000..57a5d68ec3d5
--- /dev/null
+++ b/nixos/modules/programs/skim.nix
@@ -0,0 +1,34 @@
+{ pkgs, config, lib, ... }:
+let
+  inherit (lib) mdDoc mkEnableOption mkPackageOption optional optionalString;
+  cfg = config.programs.skim;
+in
+{
+  options = {
+    programs.skim = {
+      fuzzyCompletion = mkEnableOption (mdDoc "fuzzy completion with skim");
+      keybindings = mkEnableOption (mdDoc "skim keybindings");
+      package = mkPackageOption 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/nixos/modules/programs/slock.nix b/nixos/modules/programs/slock.nix
index ce80fcc5d4a8..3db9866d9f1c 100644
--- a/nixos/modules/programs/slock.nix
+++ b/nixos/modules/programs/slock.nix
@@ -12,7 +12,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to install slock screen locker with setuid wrapper.
         '';
       };
diff --git a/nixos/modules/programs/spacefm.nix b/nixos/modules/programs/spacefm.nix
index f71abcaa3325..b4ba9dcdea56 100644
--- a/nixos/modules/programs/spacefm.nix
+++ b/nixos/modules/programs/spacefm.nix
@@ -17,8 +17,8 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to install SpaceFM and create <filename>/etc/spacefm/spacefm.conf</filename>.
+        description = lib.mdDoc ''
+          Whether to install SpaceFM and create {file}`/etc/spacefm/spacefm.conf`.
         '';
       };
 
@@ -34,10 +34,10 @@ in
             terminal_su = "''${pkgs.sudo}/bin/sudo";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           The system-wide spacefm configuration.
-          Parameters to be written to <filename>/etc/spacefm/spacefm.conf</filename>.
-          Refer to the <link xlink:href="https://ignorantguru.github.io/spacefm/spacefm-manual-en.html#programfiles-etc">relevant entry</link> in the SpaceFM manual.
+          Parameters to be written to {file}`/etc/spacefm/spacefm.conf`.
+          Refer to the [relevant entry](https://ignorantguru.github.io/spacefm/spacefm-manual-en.html#programfiles-etc) in the SpaceFM manual.
         '';
       };
 
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 75685de4f04e..36b724e04bde 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/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} "$@"
     '';
 
@@ -40,20 +41,20 @@ in
         type = types.bool;
         default = config.services.xserver.enable;
         defaultText = literalExpression "config.services.xserver.enable";
-        description = "Whether to configure SSH_ASKPASS in the environment.";
+        description = lib.mdDoc "Whether to configure SSH_ASKPASS in the environment.";
       };
 
       askPassword = mkOption {
         type = types.str;
         default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass";
         defaultText = literalExpression ''"''${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"'';
-        description = "Program used by SSH to ask for passwords.";
+        description = lib.mdDoc "Program used by SSH to ask for passwords.";
       };
 
       forwardX11 = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to request X11 forwarding on outgoing connections by default.
           This is useful for running graphical programs on the remote machine and have them display to your local X11 server.
           Historically, this value has depended on the value used by the local sshd daemon, but there really isn't a relation between the two.
@@ -66,8 +67,8 @@ in
 
       setXAuthLocation = mkOption {
         type = types.bool;
-        description = ''
-          Whether to set the path to <command>xauth</command> for X11-forwarded connections.
+        description = lib.mdDoc ''
+          Whether to set the path to {command}`xauth` for X11-forwarded connections.
           This causes a dependency on X11 packages.
         '';
       };
@@ -76,7 +77,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "ssh-ed25519" "ssh-rsa" ];
-        description = ''
+        description = lib.mdDoc ''
           Specifies the key types that will be used for public key authentication.
         '';
       };
@@ -85,7 +86,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "ssh-ed25519" "ssh-rsa" ];
-        description = ''
+        description = lib.mdDoc ''
           Specifies the host key algorithms that the client wants to use in order of preference.
         '';
       };
@@ -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 <code>Host *</code> 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.
         '';
       };
@@ -104,11 +105,11 @@ in
       startAgent = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to start the OpenSSH agent when you log in.  The OpenSSH agent
           remembers private keys for you so that you don't have to type in
           passphrases every time you make an SSH connection.  Use
-          <command>ssh-add</command> to add a key to the agent.
+          {command}`ssh-add` to add a key to the agent.
         '';
       };
 
@@ -116,7 +117,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "1h";
-        description = ''
+        description = lib.mdDoc ''
           How long to keep the private keys in memory. Use null to keep them forever.
         '';
       };
@@ -125,7 +126,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = literalExpression ''"''${pkgs.opensc}/lib/opensc-pkcs11.so"'';
-        description = ''
+        description = lib.mdDoc ''
           A pattern-list of acceptable paths for PKCS#11 shared libraries
           that may be used with the -s option to ssh-add.
         '';
@@ -135,7 +136,7 @@ in
         type = types.package;
         default = pkgs.openssh;
         defaultText = literalExpression "pkgs.openssh";
-        description = ''
+        description = lib.mdDoc ''
           The package used for the openssh client and daemon.
         '';
       };
@@ -147,7 +148,7 @@ in
             certAuthority = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 This public key is an SSH certificate authority, rather than an
                 individual host's key.
               '';
@@ -156,32 +157,32 @@ in
               type = types.listOf types.str;
               default = [ name ] ++ config.extraHostNames;
               defaultText = literalExpression "[ ${name} ] ++ config.${options.extraHostNames}";
-              description = ''
+              description = lib.mdDoc ''
                 A list of host names and/or IP numbers used for accessing
                 the host's ssh service. This list includes the name of the
-                containing <literal>knownHosts</literal> attribute by default
+                containing `knownHosts` attribute by default
                 for convenience. If you wish to configure multiple host keys
-                for the same host use multiple <literal>knownHosts</literal>
+                for the same host use multiple `knownHosts`
                 entries with different attribute names and the same
-                <literal>hostNames</literal> list.
+                `hostNames` list.
               '';
             };
             extraHostNames = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = ''
+              description = lib.mdDoc ''
                 A list of additional host names and/or IP numbers used for
                 accessing the host's ssh service. This list is ignored if
-                <literal>hostNames</literal> is set explicitly.
+                `hostNames` is set explicitly.
               '';
             };
             publicKey = mkOption {
               default = null;
               type = types.nullOr types.str;
               example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg==";
-              description = ''
+              description = lib.mdDoc ''
                 The public key data for the host. You can fetch a public key
-                from a running SSH server with the <command>ssh-keyscan</command>
+                from a running SSH server with the {command}`ssh-keyscan`
                 command. The public key should not include any host names, only
                 the key type and the key itself.
               '';
@@ -189,25 +190,25 @@ in
             publicKeyFile = mkOption {
               default = null;
               type = types.nullOr types.path;
-              description = ''
+              description = lib.mdDoc ''
                 The path to the public key file for the host. The public
                 key file is read at build time and saved in the Nix store.
                 You can fetch a public key file from a running SSH server
-                with the <command>ssh-keyscan</command> command. The content
+                with the {command}`ssh-keyscan` command. The content
                 of the file should follow the same format as described for
-                the <literal>publicKey</literal> option. Only a single key
+                the `publicKey` option. Only a single key
                 is supported. If a host has multiple keys, use
-                <option>programs.ssh.knownHostsFiles</option> instead.
+                {option}`programs.ssh.knownHostsFiles` instead.
               '';
             };
           };
         }));
-        description = ''
+        description = lib.mdDoc ''
           The set of system-wide known SSH hosts. To make simple setups more
           convenient the name of an attribute in this set is used as a host name
           for the entry. This behaviour can be disabled by setting
-          <literal>hostNames</literal> explicitly. You can use
-          <literal>extraHostNames</literal> to add additional host names without
+          `hostNames` explicitly. You can use
+          `extraHostNames` to add additional host names without
           disabling this default.
         '';
         example = literalExpression ''
@@ -228,11 +229,11 @@ in
       knownHostsFiles = mkOption {
         default = [];
         type = with types; listOf path;
-        description = ''
+        description = lib.mdDoc ''
           Files containing SSH host keys to set as global known hosts.
-          <literal>/etc/ssh/ssh_known_hosts</literal> (which is
-          generated by <option>programs.ssh.knownHosts</option>) and
-          <literal>/etc/ssh/ssh_known_hosts2</literal> are always
+          `/etc/ssh/ssh_known_hosts` (which is
+          generated by {option}`programs.ssh.knownHosts`) and
+          `/etc/ssh/ssh_known_hosts2` are always
           included.
         '';
         example = literalExpression ''
@@ -251,7 +252,7 @@ in
         type = types.nullOr (types.listOf types.str);
         default = null;
         example = [ "curve25519-sha256@libssh.org" "diffie-hellman-group-exchange-sha256" ];
-        description = ''
+        description = lib.mdDoc ''
           Specifies the available KEX (Key Exchange) algorithms.
         '';
       };
@@ -260,7 +261,7 @@ in
         type = types.nullOr (types.listOf types.str);
         default = null;
         example = [ "chacha20-poly1305@openssh.com" "aes256-gcm@openssh.com" ];
-        description = ''
+        description = lib.mdDoc ''
           Specifies the ciphers allowed and their order of preference.
         '';
       };
@@ -269,7 +270,7 @@ in
         type = types.nullOr (types.listOf types.str);
         default = null;
         example = [ "hmac-sha2-512-etm@openssh.com" "hmac-sha1" ];
-        description = ''
+        description = lib.mdDoc ''
           Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used
           for data integrity protection.
         '';
diff --git a/nixos/modules/programs/starship.nix b/nixos/modules/programs/starship.nix
index 83d2272003c6..b56c0b256164 100644
--- a/nixos/modules/programs/starship.nix
+++ b/nixos/modules/programs/starship.nix
@@ -11,13 +11,13 @@ let
 
 in {
   options.programs.starship = {
-    enable = mkEnableOption "the Starship shell prompt";
+    enable = mkEnableOption (lib.mdDoc "the Starship shell prompt");
 
     settings = mkOption {
       inherit (settingsFormat) type;
       default = { };
-      description = ''
-        Configuration included in <literal>starship.toml</literal>.
+      description = lib.mdDoc ''
+        Configuration included in `starship.toml`.
 
         See https://starship.rs/config/#prompt for documentation.
       '';
diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix
index ff4deba2bf0a..1b69aac98863 100644
--- a/nixos/modules/programs/steam.nix
+++ b/nixos/modules/programs/steam.nix
@@ -4,21 +4,35 @@ with lib;
 
 let
   cfg = config.programs.steam;
-
-  steam = pkgs.steam.override {
-    extraLibraries = pkgs: with config.hardware.opengl;
-      if pkgs.hostPlatform.is64bit
-      then [ package ] ++ extraPackages
-      else [ package32 ] ++ extraPackages32;
-  };
 in {
   options.programs.steam = {
-    enable = mkEnableOption "steam";
+    enable = mkEnableOption (lib.mdDoc "steam");
+
+    package = mkOption {
+      type        = types.package;
+      default     = pkgs.steam.override {
+        extraLibraries = pkgs: with config.hardware.opengl;
+          if pkgs.hostPlatform.is64bit
+          then [ package ] ++ extraPackages
+          else [ package32 ] ++ extraPackages32;
+      };
+      defaultText = literalExpression ''
+        pkgs.steam.override {
+          extraLibraries = pkgs: with config.hardware.opengl;
+            if pkgs.hostPlatform.is64bit
+            then [ package ] ++ extraPackages
+            else [ package32 ] ++ extraPackages32;
+        }
+      '';
+      description = lib.mdDoc ''
+        steam package to use.
+      '';
+    };
 
     remotePlay.openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Open ports in the firewall for Steam Remote Play.
       '';
     };
@@ -26,7 +40,7 @@ in {
     dedicatedServer.openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Open ports in the firewall for Source Dedicated Server.
       '';
     };
@@ -44,7 +58,10 @@ in {
 
     hardware.steam-hardware.enable = true;
 
-    environment.systemPackages = [ steam steam.run ];
+    environment.systemPackages = [
+      cfg.package
+      cfg.package.run
+    ];
 
     networking.firewall = lib.mkMerge [
       (mkIf cfg.remotePlay.openFirewall {
diff --git a/nixos/modules/programs/streamdeck-ui.nix b/nixos/modules/programs/streamdeck-ui.nix
new file mode 100644
index 000000000000..113d1d49e151
--- /dev/null
+++ b/nixos/modules/programs/streamdeck-ui.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.streamdeck-ui;
+in
+{
+  options.programs.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 = mkPackageOption pkgs "streamdeck-ui" {
+      default = [ "streamdeck-ui" ];
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [
+      cfg.package
+      (mkIf cfg.autoStart (makeAutostartItem { name = "streamdeck-ui"; package = cfg.package; }))
+    ];
+
+    services.udev.packages = [ cfg.package ];
+  };
+
+  meta.maintainers = with maintainers; [ majiir ];
+}
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index 01b047281344..b0a766dd055f 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -12,7 +12,7 @@ let
           type = types.bool;
           inherit default;
           example = !default;
-          description = "Whether to make use of the ${description}";
+          description = lib.mdDoc "Whether to make use of the ${description}";
         };
       in {
         base = mkWrapperFeature true ''
@@ -35,18 +35,18 @@ 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'');
 
     wrapperFeatures = mkOption {
       type = wrapperOptions;
       default = { };
       example = { gtk = true; };
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of features to enable in the wrapper.
       '';
     };
@@ -64,10 +64,10 @@ in {
         # use this if they aren't displayed properly:
         export _JAVA_AWT_WM_NONREPARENTING=1
       '';
-      description = ''
+      description = lib.mdDoc ''
         Shell commands executed just before Sway is started. See
-        <link xlink:href="https://github.com/swaywm/sway/wiki/Running-programs-natively-under-wayland" />
-        and <link xlink:href="https://github.com/swaywm/wlroots/blob/master/docs/env_vars.md" />
+        <https://github.com/swaywm/sway/wiki/Running-programs-natively-under-wayland>
+        and <https://github.com/swaywm/wlroots/blob/master/docs/env_vars.md>
         for some useful environment variables.
       '';
     };
@@ -79,9 +79,8 @@ in {
         "--verbose"
         "--debug"
         "--unsupported-gpu"
-        "--my-next-gpu-wont-be-nvidia"
       ];
-      description = ''
+      description = lib.mdDoc ''
         Command line arguments passed to launch Sway. Please DO NOT report
         issues if you use an unsupported GPU (proprietary drivers).
       '';
@@ -101,10 +100,10 @@ in {
           termite rofi light
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra packages to be installed system wide. See
-        <link xlink:href="https://github.com/swaywm/sway/wiki/Useful-add-ons-for-sway" /> and
-        <link xlink:href="https://github.com/swaywm/sway/wiki/i3-Migration-Guide#common-x11-apps-used-on-i3-with-wayland-alternatives" />
+        <https://github.com/swaywm/sway/wiki/Useful-add-ons-for-sway> and
+        <https://github.com/swaywm/sway/wiki/i3-Migration-Guide#common-x11-apps-used-on-i3-with-wayland-alternatives>
         for a list of useful software.
       '';
     };
diff --git a/nixos/modules/programs/sysdig.nix b/nixos/modules/programs/sysdig.nix
index fbbf29065564..ccb1e1d4c5f1 100644
--- a/nixos/modules/programs/sysdig.nix
+++ b/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/nixos/modules/programs/system-config-printer.nix b/nixos/modules/programs/system-config-printer.nix
index 34592dd7064b..7c7eea580545 100644
--- a/nixos/modules/programs/system-config-printer.nix
+++ b/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/nixos/modules/programs/systemtap.nix b/nixos/modules/programs/systemtap.nix
index 360e106678e6..cbb9ec164c6c 100644
--- a/nixos/modules/programs/systemtap.nix
+++ b/nixos/modules/programs/systemtap.nix
@@ -10,8 +10,8 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Install <command>systemtap</command> along with necessary kernel options.
+        description = lib.mdDoc ''
+          Install {command}`systemtap` along with necessary kernel options.
         '';
       };
     };
diff --git a/nixos/modules/programs/thefuck.nix b/nixos/modules/programs/thefuck.nix
index b909916158d3..e057d1ca657d 100644
--- a/nixos/modules/programs/thefuck.nix
+++ b/nixos/modules/programs/thefuck.nix
@@ -6,20 +6,23 @@ let
   prg = config.programs;
   cfg = prg.thefuck;
 
-  initScript = ''
+  bashAndZshInitScript = ''
     eval $(${pkgs.thefuck}/bin/thefuck --alias ${cfg.alias})
   '';
+  fishInitScript = ''
+    ${pkgs.thefuck}/bin/thefuck --alias ${cfg.alias} | source
+  '';
 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.
           '';
@@ -30,10 +33,8 @@ in
     config = mkIf cfg.enable {
       environment.systemPackages = with pkgs; [ thefuck ];
 
-      programs.bash.interactiveShellInit = initScript;
-      programs.zsh.interactiveShellInit = mkIf prg.zsh.enable initScript;
-      programs.fish.interactiveShellInit = mkIf prg.fish.enable ''
-        ${pkgs.thefuck}/bin/thefuck --alias | source
-      '';
+      programs.bash.interactiveShellInit = bashAndZshInitScript;
+      programs.zsh.interactiveShellInit = mkIf prg.zsh.enable bashAndZshInitScript;
+      programs.fish.interactiveShellInit = mkIf prg.fish.enable fishInitScript;
     };
   }
diff --git a/nixos/modules/programs/thunar.nix b/nixos/modules/programs/thunar.nix
new file mode 100644
index 000000000000..cb85b3886c13
--- /dev/null
+++ b/nixos/modules/programs/thunar.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.thunar;
+
+in {
+  meta = {
+    maintainers = teams.xfce.members;
+  };
+
+  options = {
+    programs.thunar = {
+      enable = mkEnableOption (lib.mdDoc "Thunar, the Xfce file manager");
+
+      plugins = mkOption {
+        default = [];
+        type = types.listOf types.package;
+        description = lib.mdDoc "List of thunar plugins to install.";
+        example = literalExpression "with pkgs.xfce; [ thunar-archive-plugin thunar-volman ]";
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable (
+    let package = pkgs.xfce.thunar.override { thunarPlugins = cfg.plugins; };
+
+    in {
+      environment.systemPackages = [
+        package
+      ];
+
+      services.dbus.packages = [
+        package
+      ];
+
+      systemd.packages = [
+        package
+      ];
+
+      programs.xfconf.enable = true;
+    }
+  );
+}
diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix
index 74b3fbd9ac06..4fb9175fb8d2 100644
--- a/nixos/modules/programs/tmux.nix
+++ b/nixos/modules/programs/tmux.nix
@@ -70,14 +70,14 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whenever to configure <command>tmux</command> system-wide.";
+        description = lib.mdDoc "Whenever to configure {command}`tmux` system-wide.";
         relatedPackages = [ "tmux" ];
       };
 
       aggressiveResize = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Resize the window to the size of the smallest session for which it is the current window.
         '';
       };
@@ -86,31 +86,31 @@ in {
         default = 0;
         example = 1;
         type = types.int;
-        description = "Base index for windows and panes.";
+        description = lib.mdDoc "Base index for windows and panes.";
       };
 
       clock24 = mkOption {
         default = false;
         type = types.bool;
-        description = "Use 24 hour clock.";
+        description = lib.mdDoc "Use 24 hour clock.";
       };
 
       customPaneNavigationAndResize = mkOption {
         default = false;
         type = types.bool;
-        description = "Override the hjkl and HJKL bindings for pane navigation and resizing in VI mode.";
+        description = lib.mdDoc "Override the hjkl and HJKL bindings for pane navigation and resizing in VI mode.";
       };
 
       escapeTime = mkOption {
         default = 500;
         example = 0;
         type = types.int;
-        description = "Time in milliseconds for which tmux waits after an escape is input.";
+        description = lib.mdDoc "Time in milliseconds for which tmux waits after an escape is input.";
       };
 
       extraConfig = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Additional contents of /etc/tmux.conf
         '';
         type = types.lines;
@@ -120,53 +120,53 @@ in {
         default = 2000;
         example = 5000;
         type = types.int;
-        description = "Maximum number of lines held in window history.";
+        description = lib.mdDoc "Maximum number of lines held in window history.";
       };
 
       keyMode = mkOption {
         default = defaultKeyMode;
         example = "vi";
         type = types.enum [ "emacs" "vi" ];
-        description = "VI or Emacs style shortcuts.";
+        description = lib.mdDoc "VI or Emacs style shortcuts.";
       };
 
       newSession = mkOption {
         default = false;
         type = types.bool;
-        description = "Automatically spawn a session if trying to attach and none are running.";
+        description = lib.mdDoc "Automatically spawn a session if trying to attach and none are running.";
       };
 
       reverseSplit = mkOption {
         default = false;
         type = types.bool;
-        description = "Reverse the window split shortcuts.";
+        description = lib.mdDoc "Reverse the window split shortcuts.";
       };
 
       resizeAmount = mkOption {
         default = defaultResize;
         example = 10;
         type = types.int;
-        description = "Number of lines/columns when resizing.";
+        description = lib.mdDoc "Number of lines/columns when resizing.";
       };
 
       shortcut = mkOption {
         default = defaultShortcut;
         example = "a";
         type = types.str;
-        description = "Ctrl following by this key is used as the main shortcut.";
+        description = lib.mdDoc "Ctrl following by this key is used as the main shortcut.";
       };
 
       terminal = mkOption {
         default = defaultTerminal;
         example = "screen-256color";
         type = types.str;
-        description = "Set the $TERM variable.";
+        description = lib.mdDoc "Set the $TERM variable.";
       };
 
       secureSocket = mkOption {
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Store tmux socket under /run, which is more secure than /tmp, but as a
           downside it doesn't survive user logout.
         '';
@@ -175,9 +175,19 @@ in {
       plugins = mkOption {
         default = [];
         type = types.listOf types.package;
-        description = "List of plugins to install.";
+        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 +203,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/nixos/modules/programs/traceroute.nix b/nixos/modules/programs/traceroute.nix
index 6e04057ac503..df5f10b87d5f 100644
--- a/nixos/modules/programs/traceroute.nix
+++ b/nixos/modules/programs/traceroute.nix
@@ -10,7 +10,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure a setcap wrapper for traceroute.
         '';
       };
diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix
index 28db96253875..7adff7cd28cb 100644
--- a/nixos/modules/programs/tsm-client.nix
+++ b/nixos/modules/programs/tsm-client.nix
@@ -26,66 +26,66 @@ let
     options.name = mkOption {
       type = servernameType;
       example = "mainTsmServer";
-      description = ''
+      description = lib.mdDoc ''
         Local name of the IBM TSM server,
         must be uncapitalized and no longer than 64 chars.
         The value will be used for the
-        <literal>server</literal>
-        directive in <filename>dsm.sys</filename>.
+        `server`
+        directive in {file}`dsm.sys`.
       '';
     };
     options.server = mkOption {
       type = nonEmptyStr;
       example = "tsmserver.company.com";
-      description = ''
+      description = lib.mdDoc ''
         Host/domain name or IP address of the IBM TSM server.
         The value will be used for the
-        <literal>tcpserveraddress</literal>
-        directive in <filename>dsm.sys</filename>.
+        `tcpserveraddress`
+        directive in {file}`dsm.sys`.
       '';
     };
     options.port = mkOption {
       type = addCheck port (p: p<=32767);
       default = 1500;  # official default
-      description = ''
+      description = lib.mdDoc ''
         TCP port of the IBM TSM server.
         The value will be used for the
-        <literal>tcpport</literal>
-        directive in <filename>dsm.sys</filename>.
+        `tcpport`
+        directive in {file}`dsm.sys`.
         TSM does not support ports above 32767.
       '';
     };
     options.node = mkOption {
       type = nonEmptyStr;
       example = "MY-TSM-NODE";
-      description = ''
+      description = lib.mdDoc ''
         Target node name on the IBM TSM server.
         The value will be used for the
-        <literal>nodename</literal>
-        directive in <filename>dsm.sys</filename>.
+        `nodename`
+        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";
-      description = ''
+      description = lib.mdDoc ''
         Directory that holds the TSM
         node's password information.
         The value will be used for the
-        <literal>passworddir</literal>
-        directive in <filename>dsm.sys</filename>.
+        `passworddir`
+        directive in {file}`dsm.sys`.
       '';
     };
     options.includeExclude = mkOption {
@@ -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 {
@@ -114,9 +114,9 @@ let
       default = {};
       example.compression = "yes";
       example.passwordaccess = null;
-      description = ''
+      description = lib.mdDoc ''
         Additional key-value pairs for the server stanza.
-        Values must be strings, or <literal>null</literal>
+        Values must be strings, or `null`
         for the key not to be used in the stanza
         (e.g. to overrule values generated by other options).
       '';
@@ -125,13 +125,13 @@ let
       type = lines;
       example = literalExpression
         ''lib.modules.mkAfter "compression no"'';
-      description = ''
+      description = lib.mdDoc ''
         Additional text lines for the server stanza.
         This option can be used if certion configuration keys
         must be used multiple times or ordered in a certain way
-        as the <option>extraConfig</option> option can't
+        as the {option}`extraConfig` option can't
         control the order of lines in the resulting stanza.
-        Note that the <literal>server</literal>
+        Note that the `server`
         line at the beginning of the stanza is
         not part of this option's value.
       '';
@@ -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:
@@ -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 = {};
@@ -185,7 +185,7 @@ let
         node = "MY-TSM-NODE";
         extraConfig.compression = "yes";
       };
-      description = ''
+      description = lib.mdDoc ''
         Server definitions ("stanzas")
         for the client system-options file.
       '';
@@ -194,20 +194,20 @@ let
       type = nullOr servernameType;
       default = null;
       example = "mainTsmServer";
-      description = ''
+      description = lib.mdDoc ''
         If multiple server stanzas are declared with
-        <option>programs.tsmClient.servers</option>,
+        {option}`programs.tsmClient.servers`,
         this option may be used to name a default
         server stanza that IBM TSM uses in the absence of
-        a user-defined <filename>dsm.opt</filename> file.
+        a user-defined {file}`dsm.opt` file.
         This option translates to a
-        <literal>defaultserver</literal> configuration line.
+        `defaultserver` configuration line.
       '';
     };
     dsmSysText = mkOption {
       type = lines;
       readOnly = true;
-      description = ''
+      description = lib.mdDoc ''
         This configuration key contains the effective text
         of the client system-options file "dsm.sys".
         It should not be changed, but may be
@@ -220,17 +220,17 @@ let
       default = pkgs.tsm-client;
       defaultText = literalExpression "pkgs.tsm-client";
       example = literalExpression "pkgs.tsm-client-withGui";
-      description = ''
+      description = lib.mdDoc ''
         The TSM client derivation to be
         added to the system environment.
-        It will called with <literal>.override</literal>
+        It will be used with `.override`
         to add paths to the client system-options file.
       '';
     };
     wrappedPackage = mkOption {
       type = package;
       readOnly = true;
-      description = ''
+      description = lib.mdDoc ''
         The TSM client derivation, wrapped with the path
         to the client system-options file "dsm.sys".
         This option is to provide the effective derivation
diff --git a/nixos/modules/programs/turbovnc.nix b/nixos/modules/programs/turbovnc.nix
index e6f8836aa367..a0e4a36cfd99 100644
--- a/nixos/modules/programs/turbovnc.nix
+++ b/nixos/modules/programs/turbovnc.nix
@@ -15,14 +15,14 @@ in
       ensureHeadlessSoftwareOpenGL = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to set up NixOS such that TurboVNC's built-in software OpenGL
           implementation works.
 
-          This will enable <option>hardware.opengl.enable</option> so that OpenGL
+          This will enable {option}`hardware.opengl.enable` so that OpenGL
           programs can find Mesa's llvmpipe drivers.
 
-          Setting this option to <code>false</code> does not mean that software
+          Setting this option to `false` does not mean that software
           OpenGL won't work; it may still work depending on your system
           configuration.
 
diff --git a/nixos/modules/programs/udevil.nix b/nixos/modules/programs/udevil.nix
index 0dc08c435df4..b0f00b4b541b 100644
--- a/nixos/modules/programs/udevil.nix
+++ b/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/nixos/modules/programs/usbtop.nix b/nixos/modules/programs/usbtop.nix
index c1b6ee38caa1..e262ae3745be 100644
--- a/nixos/modules/programs/usbtop.nix
+++ b/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/nixos/modules/programs/vim.nix b/nixos/modules/programs/vim.nix
index 1695bc994732..b12a45166d56 100644
--- a/nixos/modules/programs/vim.nix
+++ b/nixos/modules/programs/vim.nix
@@ -9,7 +9,7 @@ in {
     defaultEditor = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         When enabled, installs vim and configures vim to be the default editor
         using the EDITOR environment variable.
       '';
@@ -19,8 +19,8 @@ in {
       type = types.package;
       default = pkgs.vim;
       defaultText = literalExpression "pkgs.vim";
-      example = literalExpression "pkgs.vimHugeX";
-      description = ''
+      example = literalExpression "pkgs.vim-full";
+      description = lib.mdDoc ''
         vim package to use.
       '';
     };
diff --git a/nixos/modules/programs/wavemon.nix b/nixos/modules/programs/wavemon.nix
index e5ccacba75d4..4dbf2748913e 100644
--- a/nixos/modules/programs/wavemon.nix
+++ b/nixos/modules/programs/wavemon.nix
@@ -10,7 +10,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add wavemon to the global environment and configure a
           setcap wrapper for it.
         '';
diff --git a/nixos/modules/programs/waybar.nix b/nixos/modules/programs/waybar.nix
index 22530e6c7d4d..4697d0f7a622 100644
--- a/nixos/modules/programs/waybar.nix
+++ b/nixos/modules/programs/waybar.nix
@@ -4,7 +4,7 @@ with lib;
 
 {
   options.programs.waybar = {
-    enable = mkEnableOption "waybar";
+    enable = mkEnableOption (lib.mdDoc "waybar");
   };
 
   config = mkIf config.programs.waybar.enable {
diff --git a/nixos/modules/programs/weylus.nix b/nixos/modules/programs/weylus.nix
index ea92c77e7c32..a5775f3b981c 100644
--- a/nixos/modules/programs/weylus.nix
+++ b/nixos/modules/programs/weylus.nix
@@ -7,12 +7,12 @@ let
 in
 {
   options.programs.weylus = with types; {
-    enable = mkEnableOption "weylus";
+    enable = mkEnableOption (lib.mdDoc "weylus");
 
     openFirewall = mkOption {
       type = bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Open ports needed for the functionality of the program.
       '';
     };
@@ -20,7 +20,7 @@ in
      users = mkOption {
       type = listOf str;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         To enable stylus and multi-touch support, the user you're going to use must be added to this list.
         These users can synthesize input events system-wide, even when another user is logged in - untrusted users should not be added.
       '';
@@ -29,8 +29,8 @@ in
     package = mkOption {
       type = package;
       default = pkgs.weylus;
-      defaultText = "pkgs.weylus";
-      description = "Weylus package to install.";
+      defaultText = lib.literalExpression "pkgs.weylus";
+      description = lib.mdDoc "Weylus package to install.";
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixos/modules/programs/wireshark.nix b/nixos/modules/programs/wireshark.nix
index f7b0727cb2b3..088c2bb7958a 100644
--- a/nixos/modules/programs/wireshark.nix
+++ b/nixos/modules/programs/wireshark.nix
@@ -11,7 +11,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add Wireshark to the global environment and configure a
           setcap wrapper for 'dumpcap' for users in the 'wireshark' group.
         '';
@@ -20,7 +20,7 @@ in {
         type = types.package;
         default = pkgs.wireshark-cli;
         defaultText = literalExpression "pkgs.wireshark-cli";
-        description = ''
+        description = lib.mdDoc ''
           Which Wireshark package to install in the global environment.
         '';
       };
diff --git a/nixos/modules/programs/wshowkeys.nix b/nixos/modules/programs/wshowkeys.nix
index f7b71d2bb0c8..ebb5c5509442 100644
--- a/nixos/modules/programs/wshowkeys.nix
+++ b/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/nixos/modules/programs/xfconf.nix b/nixos/modules/programs/xfconf.nix
new file mode 100644
index 000000000000..b0f45339335d
--- /dev/null
+++ b/nixos/modules/programs/xfconf.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.xfconf;
+
+in {
+  meta = {
+    maintainers = teams.xfce.members;
+  };
+
+  options = {
+    programs.xfconf = {
+      enable = mkEnableOption (lib.mdDoc "Xfconf, the Xfce configuration storage system");
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.xfce.xfconf
+    ];
+
+    services.dbus.packages = [
+      pkgs.xfce.xfconf
+    ];
+  };
+}
diff --git a/nixos/modules/programs/xfs_quota.nix b/nixos/modules/programs/xfs_quota.nix
index c03e59a5b4ab..0fc2958b3f38 100644
--- a/nixos/modules/programs/xfs_quota.nix
+++ b/nixos/modules/programs/xfs_quota.nix
@@ -28,37 +28,37 @@ in
           options = {
             id = mkOption {
               type = types.int;
-              description = "Project ID.";
+              description = lib.mdDoc "Project ID.";
             };
 
             fileSystem = mkOption {
               type = types.str;
-              description = "XFS filesystem hosting the xfs_quota project.";
+              description = lib.mdDoc "XFS filesystem hosting the xfs_quota project.";
               default = "/";
             };
 
             path = mkOption {
               type = types.str;
-              description = "Project directory.";
+              description = lib.mdDoc "Project directory.";
             };
 
             sizeSoftLimit = mkOption {
               type = types.nullOr types.str;
               default = null;
               example = "30g";
-              description = "Soft limit of the project size";
+              description = lib.mdDoc "Soft limit of the project size";
             };
 
             sizeHardLimit = mkOption {
               type = types.nullOr types.str;
               default = null;
               example = "50g";
-              description = "Hard limit of the project size.";
+              description = lib.mdDoc "Hard limit of the project size.";
             };
           };
         });
 
-        description = "Setup of xfs_quota projects. Make sure the filesystem is mounted with the pquota option.";
+        description = lib.mdDoc "Setup of xfs_quota projects. Make sure the filesystem is mounted with the pquota option.";
 
         example = {
           projname = {
@@ -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/nixos/modules/programs/xonsh.nix b/nixos/modules/programs/xonsh.nix
index 6e40db51cdb2..7202ed06c6af 100644
--- a/nixos/modules/programs/xonsh.nix
+++ b/nixos/modules/programs/xonsh.nix
@@ -18,7 +18,7 @@ in
 
       enable = mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure xonsh as an interactive shell.
         '';
         type = types.bool;
@@ -29,14 +29,14 @@ in
         default = pkgs.xonsh;
         defaultText = literalExpression "pkgs.xonsh";
         example = literalExpression "pkgs.xonsh.override { configFile = \"/path/to/xonshrc\"; }";
-        description = ''
+        description = lib.mdDoc ''
           xonsh package to use.
         '';
       };
 
       config = mkOption {
         default = "";
-        description = "Control file to customize your shell behavior.";
+        description = lib.mdDoc "Control file to customize your shell behavior.";
         type = types.lines;
       };
 
@@ -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/nixos/modules/programs/xss-lock.nix b/nixos/modules/programs/xss-lock.nix
index aba76133e5e3..87b3957ab834 100644
--- a/nixos/modules/programs/xss-lock.nix
+++ b/nixos/modules/programs/xss-lock.nix
@@ -7,23 +7,23 @@ let
 in
 {
   options.programs.xss-lock = {
-    enable = mkEnableOption "xss-lock";
+    enable = mkEnableOption (lib.mdDoc "xss-lock");
 
     lockerCommand = mkOption {
       default = "${pkgs.i3lock}/bin/i3lock";
       defaultText = literalExpression ''"''${pkgs.i3lock}/bin/i3lock"'';
       example = literalExpression ''"''${pkgs.i3lock-fancy}/bin/i3lock-fancy"'';
       type = types.separatedString " ";
-      description = "Locker to be used with xsslock";
+      description = lib.mdDoc "Locker to be used with xsslock";
     };
 
     extraOptions = mkOption {
       default = [ ];
       example = [ "--ignore-sleep" ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         Additional command-line arguments to pass to
-        <command>xss-lock</command>.
+        {command}`xss-lock`.
       '';
     };
   };
diff --git a/nixos/modules/programs/xwayland.nix b/nixos/modules/programs/xwayland.nix
index 3a8080fa4c4d..8d13e4c22b5b 100644
--- a/nixos/modules/programs/xwayland.nix
+++ b/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;
@@ -19,7 +19,7 @@ in
       defaultText = literalExpression ''
         optionalString config.fonts.fontDir.enable "/run/current-system/sw/share/X11/fonts"
       '';
-      description = ''
+      description = lib.mdDoc ''
         Default font path. Setting this option causes Xwayland to be rebuilt.
       '';
     };
@@ -34,7 +34,7 @@ in
           inherit (config.programs.xwayland) defaultFontPath;
         })
       '';
-      description = "The Xwayland package to use.";
+      description = lib.mdDoc "The Xwayland package to use.";
     };
 
   };
diff --git a/nixos/modules/programs/yabar.nix b/nixos/modules/programs/yabar.nix
index a8fac41e899c..58ffe555715d 100644
--- a/nixos/modules/programs/yabar.nix
+++ b/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`
@@ -79,7 +79,7 @@ in
               example = "Droid Sans, FontAwesome Bold 9";
               type = types.str;
 
-              description = ''
+              description = lib.mdDoc ''
                 The font that will be used to draw the status bar.
               '';
             };
@@ -89,7 +89,7 @@ in
               example = "bottom";
               type = types.enum [ "top" "bottom" ];
 
-              description = ''
+              description = lib.mdDoc ''
                 The position where the bar will be rendered.
               '';
             };
@@ -98,7 +98,7 @@ in
               default = {};
               type = types.attrsOf types.str;
 
-              description = ''
+              description = lib.mdDoc ''
                 An attribute set which contains further attributes of a bar.
               '';
             };
@@ -109,7 +109,7 @@ in
                 options.exec = mkOption {
                   example = "YABAR_DATE";
                   type = types.str;
-                  description = ''
+                  description = lib.mdDoc ''
                      The type of the indicator to be executed.
                   '';
                 };
@@ -119,7 +119,7 @@ in
                   example = "right";
                   type = types.enum [ "left" "center" "right" ];
 
-                  description = ''
+                  description = lib.mdDoc ''
                     Whether to align the indicator at the left or right of the bar.
                   '';
                 };
@@ -128,20 +128,20 @@ in
                   default = {};
                   type = types.attrsOf (types.either types.str types.int);
 
-                  description = ''
+                  description = lib.mdDoc ''
                     An attribute set which contains further attributes of a indicator.
                   '';
                 };
               });
 
-              description = ''
+              description = lib.mdDoc ''
                 Indicators that should be rendered by yabar.
               '';
             };
           };
         });
 
-        description = ''
+        description = lib.mdDoc ''
           List of bars that should be rendered by yabar.
         '';
       };
diff --git a/nixos/modules/programs/zmap.nix b/nixos/modules/programs/zmap.nix
index 2e27fce4d7c6..056f78883061 100644
--- a/nixos/modules/programs/zmap.nix
+++ b/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/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix
index 9d7622bd3287..41ea31b0f122 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.nix
+++ b/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -41,7 +41,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable oh-my-zsh.
           '';
         };
@@ -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.
           '';
 
@@ -59,7 +59,7 @@ in
         plugins = mkOption {
           default = [];
           type = types.listOf(types.str);
-          description = ''
+          description = lib.mdDoc ''
             List of oh-my-zsh plugins
           '';
         };
@@ -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`.
           '';
         };
@@ -84,7 +84,7 @@ in
         theme = mkOption {
           default = "";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Name of the theme to be used by 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.
           '';
diff --git a/nixos/modules/programs/zsh/zsh-autoenv.nix b/nixos/modules/programs/zsh/zsh-autoenv.nix
index 62f497a45dd0..be93c96b2bc8 100644
--- a/nixos/modules/programs/zsh/zsh-autoenv.nix
+++ b/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/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
index 2e53e907d547..d3a9c372e89b 100644
--- a/nixos/modules/programs/zsh/zsh-autosuggestions.nix
+++ b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
@@ -12,19 +12,19 @@ in
 
   options.programs.zsh.autosuggestions = {
 
-    enable = mkEnableOption "zsh-autosuggestions";
+    enable = mkEnableOption (lib.mdDoc "zsh-autosuggestions");
 
     highlightStyle = mkOption {
       type = types.str;
       default = "fg=8"; # https://github.com/zsh-users/zsh-autosuggestions/tree/v0.4.3#suggestion-highlight-style
-      description = "Highlight style for suggestions ({fore,back}ground color)";
+      description = lib.mdDoc "Highlight style for suggestions ({fore,back}ground color)";
       example = "fg=cyan";
     };
 
     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:
@@ -40,14 +40,14 @@ in
     async = mkOption {
       type = types.bool;
       default = true;
-      description = "Whether to fetch suggestions asynchronously";
+      description = lib.mdDoc "Whether to fetch suggestions asynchronously";
       example = false;
     };
 
     extraConfig = mkOption {
       type = with types; attrsOf str;
       default = {};
-      description = "Attribute set with additional configuration values";
+      description = lib.mdDoc "Attribute set with additional configuration values";
       example = literalExpression ''
         {
           "ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" = "20";
diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
index 1eb53ccae52b..bc1101349283 100644
--- a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
+++ b/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" ];
@@ -30,7 +30,7 @@ in
           "line"
         ]));
 
-        description = ''
+        description = lib.mdDoc ''
           Specifies the highlighters to be used by zsh-syntax-highlighting.
 
           The following defined options can be found here:
@@ -48,7 +48,7 @@ in
           }
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Specifies custom patterns to be highlighted by zsh-syntax-highlighting.
 
           Please refer to the docs for more information about the usage:
@@ -65,7 +65,7 @@ in
           }
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Specifies custom styles to be highlighted by zsh-syntax-highlighting.
 
           Please refer to the docs for more information about the usage:
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index 5fe98b6801bb..0b152e54cf95 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/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)
   );
 
@@ -44,27 +44,27 @@ in
 
       enable = mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to configure zsh as an interactive shell. To enable zsh for
-          a particular user, use the <option>users.users.&lt;name?&gt;.shell</option>
+          a particular user, use the {option}`users.users.<name?>.shell`
           option for that user. To enable zsh system-wide use the
-          <option>users.defaultUserShell</option> option.
+          {option}`users.defaultUserShell` option.
         '';
         type = types.bool;
       };
 
       shellAliases = mkOption {
         default = { };
-        description = ''
-          Set of aliases for zsh shell, which overrides <option>environment.shellAliases</option>.
-          See <option>environment.shellAliases</option> for an option format description.
+        description = lib.mdDoc ''
+          Set of aliases for zsh shell, which overrides {option}`environment.shellAliases`.
+          See {option}`environment.shellAliases` for an option format description.
         '';
         type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during zsh shell initialisation.
         '';
         type = types.lines;
@@ -72,7 +72,7 @@ in
 
       loginShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during zsh login shell initialisation.
         '';
         type = types.lines;
@@ -80,7 +80,7 @@ in
 
       interactiveShellInit = mkOption {
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell script code called during interactive zsh shell initialisation.
         '';
         type = types.lines;
@@ -94,7 +94,7 @@ in
           # a lot of different prompt variables.
           autoload -U promptinit && promptinit && prompt suse && setopt prompt_sp
         '';
-        description = ''
+        description = lib.mdDoc ''
           Shell script code used to initialise the zsh prompt.
         '';
         type = types.lines;
@@ -102,7 +102,7 @@ in
 
       histSize = mkOption {
         default = 2000;
-        description = ''
+        description = lib.mdDoc ''
           Change history size.
         '';
         type = types.int;
@@ -110,7 +110,7 @@ in
 
       histFile = mkOption {
         default = "$HOME/.zsh_history";
-        description = ''
+        description = lib.mdDoc ''
           Change history file.
         '';
         type = types.str;
@@ -124,15 +124,15 @@ in
           "HIST_FCNTL_LOCK"
         ];
         example = [ "EXTENDED_HISTORY" "RM_STAR_WAIT" ];
-        description = ''
+        description = lib.mdDoc ''
           Configure zsh options. See
-          <citerefentry><refentrytitle>zshoptions</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+          {manpage}`zshoptions(1)`.
         '';
       };
 
       enableCompletion = mkOption {
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable zsh completion for all interactive zsh shells.
         '';
         type = types.bool;
@@ -140,7 +140,7 @@ in
 
       enableBashCompletion = mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable compatibility with bash's programmable completion system.
         '';
         type = types.bool;
@@ -149,11 +149,11 @@ in
       enableGlobalCompInit = mkOption {
         default = cfg.enableCompletion;
         defaultText = literalExpression "config.${opt.enableCompletion}";
-        description = ''
+        description = lib.mdDoc ''
           Enable execution of compinit call for all interactive zsh shells.
 
           This option can be disabled if the user wants to extend its
-          <literal>fpath</literal> and a custom <literal>compinit</literal>
+          `fpath` and a custom `compinit`
           call in the local config is required.
         '';
         type = types.bool;
@@ -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.
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 1d2262764930..aef42d0f4db1 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -30,6 +30,10 @@ with lib;
       udev rules from libu2f-host to the system. Udev gained native support
       to handle FIDO security tokens, so this isn't necessary anymore.
     '')
+    (mkRemovedOptionModule [ "hardware" "xow" ] ''
+      The xow package was removed from nixpkgs. Upstream has deprecated
+      the project and users are urged to switch to xone.
+    '')
     (mkRemovedOptionModule [ "networking" "vpnc" ] "Use environment.etc.\"vpnc/service.conf\" instead.")
     (mkRemovedOptionModule [ "networking" "wicd" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "programs" "tilp2" ] "The corresponding package was removed from nixpkgs.")
@@ -44,6 +48,7 @@ with lib;
     (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" "dd-agent" ] "dd-agent was removed from nixpkgs in favor of the newer datadog-agent.")
     (mkRemovedOptionModule [ "services" "deepin" ] "The corresponding packages were removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" ] "The corresponding package was removed from nixpkgs.")
@@ -68,6 +73,7 @@ with lib;
       prey-bash-client is deprecated upstream
     '')
     (mkRemovedOptionModule [ "services" "quagga" ] "the corresponding package has been removed from nixpkgs")
+    (mkRemovedOptionModule [ "services" "railcar" ] "the corresponding package has been removed from nixpkgs")
     (mkRemovedOptionModule [ "services" "seeks" ] "")
     (mkRemovedOptionModule [ "services" "ssmtp" ] ''
       The ssmtp package and the corresponding module have been removed due to
@@ -97,6 +103,8 @@ with lib;
     (mkRemovedOptionModule [ "services" "gogoclient" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "virtuoso" ] "The corresponding package was removed from nixpkgs.")
     (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.")
 
     # Do NOT add any option renames here, see top of the file
   ];
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index d827c448055b..a380bb5484af 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/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}";
 
@@ -325,6 +325,7 @@ let
         '');
       } // optionalAttrs (data.listenHTTP != null && toInt (elemAt (splitString ":" data.listenHTTP) 1) < 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.
@@ -445,20 +447,19 @@ let
       validMinDays = mkOption {
         type = types.int;
         inherit (defaultAndText "validMinDays" 30) default defaultText;
-        description = "Minimum remaining validity before renewal in days.";
+        description = lib.mdDoc "Minimum remaining validity before renewal in days.";
       };
 
       renewInterval = mkOption {
         type = types.str;
         inherit (defaultAndText "renewInterval" "daily") default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Systemd calendar expression when to check for renewal. See
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
 
-      enableDebugLogs = mkEnableOption "debug logging for this certificate" // {
+      enableDebugLogs = mkEnableOption (lib.mdDoc "debug logging for this certificate") // {
         inherit (defaultAndText "enableDebugLogs" true) default defaultText;
       };
 
@@ -466,11 +467,11 @@ let
         type = types.nullOr types.str;
         inherit (defaultAndText "webroot" null) default defaultText;
         example = "/var/lib/acme/acme-challenge";
-        description = ''
+        description = lib.mdDoc ''
           Where the webroot of the HTTP vhost is located.
-          <filename>.well-known/acme-challenge/</filename> directory
+          {file}`.well-known/acme-challenge/` directory
           will be created below the webroot if it doesn't exist.
-          <literal>http://example.org/.well-known/acme-challenge/</literal> must also
+          `http://example.org/.well-known/acme-challenge/` must also
           be available (notice unencrypted HTTP).
         '';
       };
@@ -478,17 +479,17 @@ let
       server = mkOption {
         type = types.nullOr types.str;
         inherit (defaultAndText "server" null) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           ACME Directory Resource URI. Defaults to Let's Encrypt's
           production endpoint,
-          <link xlink:href="https://acme-v02.api.letsencrypt.org/directory"/>, if unset.
+          <https://acme-v02.api.letsencrypt.org/directory>, if unset.
         '';
       };
 
       email = mkOption {
         type = types.str;
         inherit (defaultAndText "email" null) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Email address for account creation and correspondence from the CA.
           It is recommended to use the same email for all certs to avoid account
           creation limits.
@@ -498,14 +499,14 @@ let
       group = mkOption {
         type = types.str;
         inherit (defaultAndText "group" "acme") default defaultText;
-        description = "Group running the ACME client.";
+        description = lib.mdDoc "Group running the ACME client.";
       };
 
       reloadServices = mkOption {
         type = types.listOf types.str;
         inherit (defaultAndText "reloadServices" []) default defaultText;
-        description = ''
-          The list of systemd services to call <code>systemctl try-reload-or-restart</code>
+        description = lib.mdDoc ''
+          The list of systemd services to call `systemctl try-reload-or-restart`
           on.
         '';
       };
@@ -514,7 +515,7 @@ let
         type = types.lines;
         inherit (defaultAndText "postRun" "") default defaultText;
         example = "cp full.pem backup.pem";
-        description = ''
+        description = lib.mdDoc ''
           Commands to run after new certificates go live. Note that
           these commands run as the root user.
 
@@ -525,10 +526,10 @@ let
       keyType = mkOption {
         type = types.str;
         inherit (defaultAndText "keyType" "ec256") default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Key type to use for private keys.
           For an up to date list of supported values check the --key-type option
-          at <link xlink:href="https://go-acme.github.io/lego/usage/cli/#usage"/>.
+          at <https://go-acme.github.io/lego/usage/cli/#usage>.
         '';
       };
 
@@ -536,9 +537,9 @@ let
         type = types.nullOr types.str;
         inherit (defaultAndText "dnsProvider" null) default defaultText;
         example = "route53";
-        description = ''
+        description = lib.mdDoc ''
           DNS Challenge provider. For a list of supported providers, see the "code"
-          field of the DNS providers listed at <link xlink:href="https://go-acme.github.io/lego/dns/"/>.
+          field of the DNS providers listed at <https://go-acme.github.io/lego/dns/>.
         '';
       };
 
@@ -546,7 +547,7 @@ let
         type = types.nullOr types.str;
         inherit (defaultAndText "dnsResolver" null) default defaultText;
         example = "1.1.1.1:53";
-        description = ''
+        description = lib.mdDoc ''
           Set the resolver to use for performing recursive DNS queries. Supported:
           host:port. The default is to use the system resolvers, or Google's DNS
           resolvers if the system's cannot be determined.
@@ -556,11 +557,11 @@ let
       credentialsFile = mkOption {
         type = types.path;
         inherit (defaultAndText "credentialsFile" null) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Path to an EnvironmentFile for the cert's service containing any required and
           optional environment variables for your selected dnsProvider.
           To find out what values you need to set, consult the documentation at
-          <link xlink:href="https://go-acme.github.io/lego/dns/"/> for the corresponding dnsProvider.
+          <https://go-acme.github.io/lego/dns/> for the corresponding dnsProvider.
         '';
         example = "/var/src/secrets/example.org-route53-api-token";
       };
@@ -568,7 +569,7 @@ let
       dnsPropagationCheck = mkOption {
         type = types.bool;
         inherit (defaultAndText "dnsPropagationCheck" true) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Toggles lego DNS propagation check, which is used alongside DNS-01
           challenge to ensure the DNS entries required are available.
         '';
@@ -577,20 +578,19 @@ 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>
         '';
       };
 
       extraLegoFlags = mkOption {
         type = types.listOf types.str;
         inherit (defaultAndText "extraLegoFlags" []) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Additional global flags to pass to all lego commands.
         '';
       };
@@ -598,7 +598,7 @@ let
       extraLegoRenewFlags = mkOption {
         type = types.listOf types.str;
         inherit (defaultAndText "extraLegoRenewFlags" []) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Additional flags to pass to lego renew.
         '';
       };
@@ -606,7 +606,7 @@ let
       extraLegoRunFlags = mkOption {
         type = types.listOf types.str;
         inherit (defaultAndText "extraLegoRunFlags" []) default defaultText;
-        description = ''
+        description = lib.mdDoc ''
           Additional flags to pass to lego run.
         '';
       };
@@ -637,13 +637,13 @@ let
         type = types.str;
         readOnly = true;
         default = "/var/lib/acme/${name}";
-        description = "Directory where certificate and other state is stored.";
+        description = lib.mdDoc "Directory where certificate and other state is stored.";
       };
 
       domain = mkOption {
         type = types.str;
         default = name;
-        description = "Domain to fetch certificate for (defaults to the entry name).";
+        description = lib.mdDoc "Domain to fetch certificate for (defaults to the entry name).";
       };
 
       extraDomainNames = mkOption {
@@ -655,7 +655,7 @@ let
             "mydomain.org"
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           A list of extra domain names, which are included in the one certificate to be issued.
         '';
       };
@@ -667,7 +667,7 @@ let
         type = types.nullOr types.str;
         default = null;
         example = ":1360";
-        description = ''
+        description = lib.mdDoc ''
           Interface and port to listen on to solve HTTP challenges
           in the form [INTERFACE]:PORT.
           If you use a port other than 80, you must proxy port 80 to this port.
@@ -677,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;
       };
     };
@@ -690,7 +690,7 @@ in {
       preliminarySelfsigned = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether a preliminary self-signed certificate should be generated before
           doing ACME requests. This can be useful when certificates are required in
           a webserver, but ACME needs the webserver to make its requests.
@@ -703,18 +703,18 @@ in {
       acceptTerms = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Accept the CA's terms of service. The default provider is Let's Encrypt,
-          you can find their ToS at <link xlink:href="https://letsencrypt.org/repository/"/>.
+          you can find their ToS at <https://letsencrypt.org/repository/>.
         '';
       };
 
       useRoot = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        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.
@@ -723,7 +723,7 @@ in {
 
       defaults = mkOption {
         type = types.submodule (inheritableModule true);
-        description = ''
+        description = lib.mdDoc ''
           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
@@ -734,9 +734,9 @@ in {
       certs = mkOption {
         default = { };
         type = with types; attrsOf (submodule [ (inheritableModule false) certOpts ]);
-        description = ''
+        description = lib.mdDoc ''
           Attribute set of certificates to get signed and renewed. Creates
-          <literal>acme-''${cert}.{service,timer}</literal> systemd units for
+          `acme-''${cert}.{service,timer}` systemd units for
           each certificate defined here. Other services can add dependencies
           to those units if they rely on the certificates being present,
           or trigger restarts of the service if certificates get renewed.
@@ -765,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)))
diff --git a/nixos/modules/security/acme/doc.xml b/nixos/modules/security/acme/doc.xml
index f623cc509be6..1439594a5aca 100644
--- a/nixos/modules/security/acme/doc.xml
+++ b/nixos/modules/security/acme/doc.xml
@@ -81,8 +81,8 @@ services.nginx = {
     };
 
     # We can also add a different vhost and reuse the same certificate
-    # but we have to append extraDomainNames manually.
-    <link linkend="opt-security.acme.certs._name_.extraDomainNames">security.acme.certs."foo.example.com".extraDomainNames</link> = [ "baz.example.com" ];
+    # 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";
@@ -237,8 +237,8 @@ services.bind = {
 
 <programlisting>
 systemd.services.dns-rfc2136-conf = {
-  requiredBy = ["acme-example.com.service", "bind.service"];
-  before = ["acme-example.com.service", "bind.service"];
+  requiredBy = ["acme-example.com.service" "bind.service"];
+  before = ["acme-example.com.service" "bind.service"];
   unitConfig = {
     ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
   };
@@ -249,18 +249,19 @@ systemd.services.dns-rfc2136-conf = {
   path = [ pkgs.bind ];
   script = ''
     mkdir -p /var/lib/secrets
+    chmod 755 /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
+    # extract secret value from the dnskeys.conf
+    while read x y; do if [ "$x" = "secret" ]; then secret="''${y:1:''${#y}-3}"; fi; done &lt; /var/lib/secrets/dnskeys.conf
 
     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'
+    RFC2136_TSIG_SECRET='$secret'
     EOF
     chmod 400 /var/lib/secrets/certs.secret
   '';
diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix
index be1b0362fc13..24b48338ed77 100644
--- a/nixos/modules/security/apparmor.nix
+++ b/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,11 +38,11 @@ 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 = ''
+        description = lib.mdDoc ''
           AppArmor policies.
         '';
         type = types.attrsOf (types.submodule ({ name, config, ... }: {
@@ -50,7 +50,7 @@ in
             enable = mkDisableOption "loading of the profile into the kernel";
             enforce = mkDisableOption "enforcing of the policy or only complain in the logs";
             profile = mkOption {
-              description = "The policy of the profile.";
+              description = lib.mdDoc "The policy of the profile.";
               type = types.lines;
               apply = pkgs.writeText name;
             };
@@ -61,34 +61,34 @@ in
       includes = mkOption {
         type = types.attrsOf types.lines;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           List of paths to be added to AppArmor's searched paths
-          when resolving <literal>include</literal> directives.
+          when resolving `include` directives.
         '';
         apply = mapAttrs pkgs.writeText;
       };
       packages = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = "List of packages to be added to AppArmor's include path";
+        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/nixos/modules/security/audit.nix b/nixos/modules/security/audit.nix
index 2b22bdd9f0ae..06b4766c8f5a 100644
--- a/nixos/modules/security/audit.nix
+++ b/nixos/modules/security/audit.nix
@@ -56,7 +56,7 @@ in {
       enable = mkOption {
         type        = types.enum [ false true "lock" ];
         default     = false;
-        description = ''
+        description = lib.mdDoc ''
           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
@@ -67,13 +67,13 @@ in {
       failureMode = mkOption {
         type        = types.enum [ "silent" "printk" "panic" ];
         default     = "printk";
-        description = "How to handle critical errors in the auditing system";
+        description = lib.mdDoc "How to handle critical errors in the auditing system";
       };
 
       backlogLimit = mkOption {
         type        = types.int;
         default     = 64; # Apparently the kernel default
-        description = ''
+        description = lib.mdDoc ''
           The maximum number of outstanding audit buffers allowed; exceeding this is
           considered a failure and handled in a manner specified by failureMode.
         '';
@@ -82,7 +82,7 @@ in {
       rateLimit = mkOption {
         type        = types.int;
         default     = 0;
-        description = ''
+        description = lib.mdDoc ''
           The maximum messages per second permitted before triggering a failure as
           specified by failureMode. Setting it to zero disables the limit.
         '';
@@ -92,7 +92,7 @@ in {
         type        = types.listOf types.str; # (types.either types.str (types.submodule rule));
         default     = [];
         example     = [ "-a exit,always -F arch=b64 -S execve" ];
-        description = ''
+        description = lib.mdDoc ''
           The ordered audit rules, with each string appearing as one line of the audit.rules file.
         '';
       };
diff --git a/nixos/modules/security/auditd.nix b/nixos/modules/security/auditd.nix
index 9d26cfbcfb10..db4b2701ee2e 100644
--- a/nixos/modules/security/auditd.nix
+++ b/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/nixos/modules/security/ca.nix b/nixos/modules/security/ca.nix
index f71d9d90ec5b..c704e2c1f51c 100644
--- a/nixos/modules/security/ca.nix
+++ b/nixos/modules/security/ca.nix
@@ -23,12 +23,12 @@ in
       type = types.listOf types.path;
       default = [];
       example = literalExpression ''[ "''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ]'';
-      description = ''
+      description = lib.mdDoc ''
         A list of files containing trusted root certificates in PEM
         format. These are concatenated to form
-        <filename>/etc/ssl/certs/ca-certificates.crt</filename>, which is
+        {file}`/etc/ssl/certs/ca-certificates.crt`, which is
         used by many programs that use OpenSSL, such as
-        <command>curl</command> and <command>git</command>.
+        {command}`curl` and {command}`git`.
       '';
     };
 
@@ -47,7 +47,7 @@ in
           '''
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         A list of trusted root certificates in PEM format.
       '';
     };
@@ -60,10 +60,10 @@ in
         "CA WoSign ECC Root"
         "Certification Authority of WoSign G2"
       ];
-      description = ''
+      description = lib.mdDoc ''
         A list of blacklisted CA certificate names that won't be imported from
         the Mozilla Trust Store into
-        <filename>/etc/ssl/certs/ca-certificates.crt</filename>. Use the
+        {file}`/etc/ssl/certs/ca-certificates.crt`. Use the
         names from that file.
       '';
     };
diff --git a/nixos/modules/security/chromium-suid-sandbox.nix b/nixos/modules/security/chromium-suid-sandbox.nix
index bb99c053f718..cab4b9f8d3ab 100644
--- a/nixos/modules/security/chromium-suid-sandbox.nix
+++ b/nixos/modules/security/chromium-suid-sandbox.nix
@@ -14,7 +14,7 @@ in
   options.security.chromiumSuidSandbox.enable = mkOption {
     type = types.bool;
     default = false;
-    description = ''
+    description = lib.mdDoc ''
       Whether to install the Chromium SUID sandbox which is an executable that
       Chromium may use in order to achieve sandboxing.
 
diff --git a/nixos/modules/security/dhparams.nix b/nixos/modules/security/dhparams.nix
index cfa9003f12fb..9fed7e012b1e 100644
--- a/nixos/modules/security/dhparams.nix
+++ b/nixos/modules/security/dhparams.nix
@@ -15,7 +15,7 @@ let
       type = bitType;
       default = cfg.defaultBitSize;
       defaultText = literalExpression "config.${opt.defaultBitSize}";
-      description = ''
+      description = lib.mdDoc ''
         The bit size for the prime that is used during a Diffie-Hellman
         key exchange.
       '';
@@ -24,11 +24,11 @@ let
     options.path = mkOption {
       type = types.path;
       readOnly = true;
-      description = ''
+      description = lib.mdDoc ''
         The resulting path of the generated Diffie-Hellman parameters
         file for other services to reference. This could be either a
         store path or a file inside the directory specified by
-        <option>security.dhparams.path</option>.
+        {option}`security.dhparams.path`.
       '';
     };
 
@@ -45,7 +45,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to generate new DH params and clean up old DH params.
         '';
       };
@@ -56,66 +56,74 @@ 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.<replaceable>name</replaceable>.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.
+          :::
         '';
       };
 
       defaultBitSize = mkOption {
         type = bitType;
         default = 2048;
-        description = ''
+        description = lib.mdDoc ''
           This allows to override the default bit size for all of the
           Diffie-Hellman parameters set in
-          <option>security.dhparams.params</option>.
+          {option}`security.dhparams.params`.
         '';
       };
 
       path = mkOption {
         type = types.str;
         default = "/var/lib/dhparams";
-        description = ''
+        description = lib.mdDoc ''
           Path to the directory in which Diffie-Hellman parameters will be
           stored. This only is relevant if
-          <option>security.dhparams.stateful</option> is
-          <literal>true</literal>.
+          {option}`security.dhparams.stateful` is
+          `true`.
         '';
       };
     };
diff --git a/nixos/modules/security/doas.nix b/nixos/modules/security/doas.nix
index 2a814f17e454..4d15ed9a8025 100644
--- a/nixos/modules/security/doas.nix
+++ b/nixos/modules/security/doas.nix
@@ -53,8 +53,8 @@ in
     enable = mkOption {
       type = with types; bool;
       default = false;
-      description = ''
-        Whether to enable the <command>doas</command> command, which allows
+      description = lib.mdDoc ''
+        Whether to enable the {command}`doas` command, which allows
         non-root users to execute commands as root.
       '';
     };
@@ -62,19 +62,19 @@ in
     wheelNeedsPassword = mkOption {
       type = with types; bool;
       default = true;
-      description = ''
-        Whether users of the <code>wheel</code> group must provide a password to
-        run commands as super user via <command>doas</command>.
+      description = lib.mdDoc ''
+        Whether users of the `wheel` group must provide a password to
+        run commands as super user via {command}`doas`.
       '';
     };
 
     extraRules = mkOption {
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Define specific rules to be set in the
-        <filename>/etc/doas.conf</filename> file. More specific rules should
+        {file}`/etc/doas.conf` file. More specific rules should
         come after more general ones in order to yield the expected behavior.
-        You can use <code>mkBefore</code> and/or <code>mkAfter</code> to ensure
+        You can use `mkBefore` and/or `mkAfter` to ensure
         this is the case when configuration options are merged.
       '';
       example = literalExpression ''
@@ -113,8 +113,8 @@ in
             noPass = mkOption {
               type = with types; bool;
               default = false;
-              description = ''
-                If <code>true</code>, the user is not required to enter a
+              description = lib.mdDoc ''
+                If `true`, the user is not required to enter a
                 password.
               '';
             };
@@ -122,18 +122,18 @@ in
             noLog = mkOption {
               type = with types; bool;
               default = false;
-              description = ''
-                If <code>true</code>, successful executions will not be logged
+              description = lib.mdDoc ''
+                If `true`, successful executions will not be logged
                 to
-                <citerefentry><refentrytitle>syslogd</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+                {manpage}`syslogd(8)`.
               '';
             };
 
             persist = mkOption {
               type = with types; bool;
               default = false;
-              description = ''
-                If <code>true</code>, do not ask for a password again for some
+              description = lib.mdDoc ''
+                If `true`, do not ask for a password again for some
                 time after the user successfully authenticates.
               '';
             };
@@ -141,10 +141,10 @@ in
             keepEnv = mkOption {
               type = with types; bool;
               default = false;
-              description = ''
-                If <code>true</code>, environment variables other than those
+              description = lib.mdDoc ''
+                If `true`, environment variables other than those
                 listed in
-                <citerefentry><refentrytitle>doas</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+                {manpage}`doas(1)`
                 are kept when creating the environment for the new process.
               '';
             };
@@ -152,18 +152,18 @@ in
             setEnv = mkOption {
               type = with types; listOf str;
               default = [];
-              description = ''
+              description = lib.mdDoc ''
                 Keep or set the specified variables. Variables may also be
                 removed with a leading '-' or set using
-                <code>variable=value</code>. If the first character of
-                <code>value</code> is a '$', the value to be set is taken from
+                `variable=value`. If the first character of
+                `value` is a '$', the value to be set is taken from
                 the existing environment variable of the indicated name. This
                 option is processed after the default environment has been
                 created.
 
-                NOTE: All rules have <code>setenv { SSH_AUTH_SOCK }</code> by
-                default. To prevent <code>SSH_AUTH_SOCK</code> from being
-                inherited, add <code>"-SSH_AUTH_SOCK"</code> anywhere in this
+                NOTE: All rules have `setenv { SSH_AUTH_SOCK }` by
+                default. To prevent `SSH_AUTH_SOCK` from being
+                inherited, add `"-SSH_AUTH_SOCK"` anywhere in this
                 list.
               '';
             };
@@ -171,35 +171,35 @@ in
             users = mkOption {
               type = with types; listOf (either str int);
               default = [];
-              description = "The usernames / UIDs this rule should apply for.";
+              description = lib.mdDoc "The usernames / UIDs this rule should apply for.";
             };
 
             groups = mkOption {
               type = with types; listOf (either str int);
               default = [];
-              description = "The groups / GIDs this rule should apply for.";
+              description = lib.mdDoc "The groups / GIDs this rule should apply for.";
             };
 
             runAs = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Which user or group the specified command is allowed to run as.
-                When set to <code>null</code> (the default), all users are
+                When set to `null` (the default), all users are
                 allowed.
 
                 A user can be specified using just the username:
-                <code>"foo"</code>. It is also possible to only allow running as
-                a specific group with <code>":bar"</code>.
+                `"foo"`. It is also possible to only allow running as
+                a specific group with `":bar"`.
               '';
             };
 
             cmd = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The command the user is allowed to run. When set to
-                <code>null</code> (the default), all commands are allowed.
+                `null` (the default), all commands are allowed.
 
                 NOTE: It is best practice to specify absolute paths. If a
                 relative path is specified, only a restricted PATH will be
@@ -210,9 +210,9 @@ in
             args = mkOption {
               type = with types; nullOr (listOf str);
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Arguments that must be provided to the command. When set to
-                <code>[]</code>, the command must be run without any arguments.
+                `[]`, the command must be run without any arguments.
               '';
             };
           };
@@ -223,8 +223,8 @@ in
     extraConfig = mkOption {
       type = with types; lines;
       default = "";
-      description = ''
-        Extra configuration text appended to <filename>doas.conf</filename>.
+      description = lib.mdDoc ''
+        Extra configuration text appended to {file}`doas.conf`.
       '';
     };
   };
diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix
index bbe246fe229e..02b11766b3c0 100644
--- a/nixos/modules/security/duosec.nix
+++ b/nixos/modules/security/duosec.nix
@@ -36,24 +36,24 @@ in
       ssh.enable = mkOption {
         type = types.bool;
         default = false;
-        description = "If enabled, protect SSH logins with Duo Security.";
+        description = lib.mdDoc "If enabled, protect SSH logins with Duo Security.";
       };
 
       pam.enable = mkOption {
         type = types.bool;
         default = false;
-        description = "If enabled, protect logins with Duo Security using PAM support.";
+        description = lib.mdDoc "If enabled, protect logins with Duo Security using PAM support.";
       };
 
       integrationKey = mkOption {
         type = types.str;
-        description = "Integration key.";
+        description = lib.mdDoc "Integration key.";
       };
 
       secretKeyFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A file containing your secret key. The security of your Duo application is tied to the security of your secret key.
         '';
         example = "/run/keys/duo-skey";
@@ -61,25 +61,25 @@ in
 
       host = mkOption {
         type = types.str;
-        description = "Duo API hostname.";
+        description = lib.mdDoc "Duo API hostname.";
       };
 
       groups = mkOption {
         type = types.str;
         default = "";
         example = "users,!wheel,!*admin guests";
-        description = ''
+        description = lib.mdDoc ''
           If specified, Duo authentication is required only for users
           whose primary group or supplementary group list matches one
           of the space-separated pattern lists. Refer to
-          <link xlink:href="https://duo.com/docs/duounix"/> for details.
+          <https://duo.com/docs/duounix> for details.
         '';
       };
 
       failmode = mkOption {
         type = types.enum [ "safe" "secure" ];
         default = "safe";
-        description = ''
+        description = lib.mdDoc ''
           On service or configuration errors that prevent Duo
           authentication, fail "safe" (allow access) or "secure" (deny
           access). The default is "safe".
@@ -89,7 +89,7 @@ in
       pushinfo = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Include information such as the command to be executed in
           the Duo Push message.
         '';
@@ -98,22 +98,22 @@ in
       autopush = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          If <literal>true</literal>, Duo Unix will automatically send
+        description = lib.mdDoc ''
+          If `true`, Duo Unix will automatically send
           a push login request to the user’s phone, falling back on a
           phone call if push is unavailable. If
-          <literal>false</literal>, the user will be prompted to
+          `false`, the user will be prompted to
           choose an authentication method. When configured with
-          <literal>autopush = yes</literal>, we recommend setting
-          <literal>prompts = 1</literal>.
+          `autopush = yes`, we recommend setting
+          `prompts = 1`.
         '';
       };
 
       motd = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Print the contents of <literal>/etc/motd</literal> to screen
+        description = lib.mdDoc ''
+          Print the contents of `/etc/motd` to screen
           after a successful login.
         '';
       };
@@ -121,30 +121,30 @@ in
       prompts = mkOption {
         type = types.enum [ 1 2 3 ];
         default = 3;
-        description = ''
+        description = lib.mdDoc ''
           If a user fails to authenticate with a second factor, Duo
           Unix will prompt the user to authenticate again. This option
           sets the maximum number of prompts that Duo Unix will
           display before denying access. Must be 1, 2, or 3. Default
           is 3.
 
-          For example, when <literal>prompts = 1</literal>, the user
+          For example, when `prompts = 1`, the user
           will have to successfully authenticate on the first prompt,
-          whereas if <literal>prompts = 2</literal>, if the user
+          whereas if `prompts = 2`, if the user
           enters incorrect information at the initial prompt, he/she
           will be prompted to authenticate again.
 
-          When configured with <literal>autopush = true</literal>, we
-          recommend setting <literal>prompts = 1</literal>.
+          When configured with `autopush = true`, we
+          recommend setting `prompts = 1`.
         '';
       };
 
       acceptEnvFactor = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Look for factor selection or passcode in the
-          <literal>$DUO_PASSCODE</literal> environment variable before
+          `$DUO_PASSCODE` environment variable before
           prompting the user for input.
 
           When $DUO_PASSCODE is non-empty, it will override
@@ -157,11 +157,11 @@ in
       fallbackLocalIP = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Duo Unix reports the IP address of the authorizing user, for
           the purposes of authorization and whitelisting. If Duo Unix
           cannot detect the IP address of the client, setting
-          <literal>fallbackLocalIP = yes</literal> will cause Duo Unix
+          `fallbackLocalIP = yes` will cause Duo Unix
           to send the IP address of the server it is running on.
 
           If you are using IP whitelisting, enabling this option could
@@ -173,7 +173,7 @@ in
       allowTcpForwarding = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           By default, when SSH forwarding, enabling Duo Security will
           disable TCP forwarding. By enabling this, you potentially
           undermine some of the SSH based login security. Note this is
diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix
index cf416035ef60..f75b4df1851a 100644
--- a/nixos/modules/security/google_oslogin.nix
+++ b/nixos/modules/security/google_oslogin.nix
@@ -16,7 +16,7 @@ in
     security.googleOsLogin.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable Google OS Login.
 
         The OS Login package enables the following components:
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
index 065587bc286e..674ba857818c 100644
--- a/nixos/modules/security/lock-kernel-modules.nix
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -11,11 +11,11 @@ with lib;
     security.lockKernelModules = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Disable kernel module loading once the system is fully initialised.
         Module loading is disabled until the next reboot. Problems caused
         by delayed module loading can be fixed by adding the module(s) in
-        question to <option>boot.kernelModules</option>.
+        question to {option}`boot.kernelModules`.
       '';
     };
   };
diff --git a/nixos/modules/security/misc.nix b/nixos/modules/security/misc.nix
index c20e067b8cc7..cd48eade7784 100644
--- a/nixos/modules/security/misc.nix
+++ b/nixos/modules/security/misc.nix
@@ -15,7 +15,7 @@ with lib;
     security.allowUserNamespaces = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to allow creation of user namespaces.
 
         The motivation for disabling user namespaces is the potential
@@ -34,7 +34,7 @@ with lib;
     security.unprivilegedUsernsClone = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         When disabled, unprivileged users will not be able to create new namespaces.
         By default unprivileged user namespaces are disabled.
         This option only works in a hardened profile.
@@ -44,7 +44,7 @@ with lib;
     security.protectKernelImage = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to prevent replacing the running kernel image.
       '';
     };
@@ -52,7 +52,7 @@ with lib;
     security.allowSimultaneousMultithreading = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to allow SMT/hyperthreading.  Disabling SMT means that only
         physical CPU cores will be usable at runtime, potentially at
         significant performance cost.
@@ -62,7 +62,7 @@ with lib;
         e.g., shared caches).  This attack vector is unproven.
 
         Disabling SMT is a supplement to the L1 data cache flushing mitigation
-        (see <xref linkend="opt-security.virtualisation.flushL1DataCache"/>)
+        (see [](#opt-security.virtualisation.flushL1DataCache))
         versus malicious VM guests (SMT could "bring back" previously flushed
         data).
       '';
@@ -71,7 +71,7 @@ with lib;
     security.forcePageTableIsolation = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to force-enable the Page Table Isolation (PTI) Linux kernel
         feature even on CPU models that claim to be safe from Meltdown.
 
@@ -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/nixos/modules/security/oath.nix b/nixos/modules/security/oath.nix
index 93bdc851117a..334286653846 100644
--- a/nixos/modules/security/oath.nix
+++ b/nixos/modules/security/oath.nix
@@ -11,7 +11,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the OATH (one-time password) PAM module.
         '';
       };
@@ -19,7 +19,7 @@ with lib;
       digits = mkOption {
         type = types.enum [ 6 7 8 ];
         default = 6;
-        description = ''
+        description = lib.mdDoc ''
           Specify the length of the one-time password in number of
           digits.
         '';
@@ -28,7 +28,7 @@ with lib;
       window = mkOption {
         type = types.int;
         default = 5;
-        description = ''
+        description = lib.mdDoc ''
           Specify the number of one-time passwords to check in order
           to accommodate for situations where the system and the
           client are slightly out of sync (iteration for HOTP or time
@@ -39,7 +39,7 @@ with lib;
       usersFile = mkOption {
         type = types.path;
         default = "/etc/users.oath";
-        description = ''
+        description = lib.mdDoc ''
           Set the path to file where the user's credentials are
           stored. This file must not be world readable!
         '';
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 530304b497ae..273bc796341c 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -15,24 +15,24 @@ let
       name = mkOption {
         example = "sshd";
         type = types.str;
-        description = "Name of the PAM service.";
+        description = lib.mdDoc "Name of the PAM service.";
       };
 
       unixAuth = mkOption {
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether users can log in with passwords defined in
-          <filename>/etc/shadow</filename>.
+          {file}`/etc/shadow`.
         '';
       };
 
       rootOK = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, root doesn't need to authenticate (e.g. for the
-          <command>useradd</command> service).
+          {command}`useradd` service).
         '';
       };
 
@@ -40,10 +40,10 @@ let
         default = config.security.pam.p11.enable;
         defaultText = literalExpression "config.security.pam.p11.enable";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, keys listed in
-          <filename>~/.ssh/authorized_keys</filename> and
-          <filename>~/.eid/authorized_certificates</filename>
+          {file}`~/.ssh/authorized_keys` and
+          {file}`~/.eid/authorized_certificates`
           can be used to log in with the associated PKCS#11 tokens.
         '';
       };
@@ -52,24 +52,24 @@ let
         default = config.security.pam.u2f.enable;
         defaultText = literalExpression "config.security.pam.u2f.enable";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, users listed in
-          <filename>$XDG_CONFIG_HOME/Yubico/u2f_keys</filename> (or
-          <filename>$HOME/.config/Yubico/u2f_keys</filename> if XDG variable is
+          {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
+          {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
           not set) are able to log in with the associated U2F key. Path can be
-          changed using <option>security.pam.u2f.authFile</option> option.
+          changed using {option}`security.pam.u2f.authFile` option.
         '';
       };
 
       usshAuth = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, users with an SSH certificate containing an authorized principal
           in their SSH agent are able to log in. Specific options are controlled
-          using the <option>security.pam.ussh</option> options.
+          using the {option}`security.pam.ussh` options.
 
-          Note that the  <option>security.pam.ussh.enable</option> must also be
+          Note that the  {option}`security.pam.ussh.enable` must also be
           set for this option to take effect.
         '';
       };
@@ -78,9 +78,9 @@ let
         default = config.security.pam.yubico.enable;
         defaultText = literalExpression "config.security.pam.yubico.enable";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, users listed in
-          <filename>~/.yubico/authorized_yubikeys</filename>
+          {file}`~/.yubico/authorized_yubikeys`
           are able to log in with the associated Yubikey tokens.
         '';
       };
@@ -89,9 +89,9 @@ let
         enable = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             If set, users with enabled Google Authenticator (created
-            <filename>~/.google_authenticator</filename>) will be required
+            {file}`~/.google_authenticator`) will be required
             to provide Google Authenticator token to log in.
           '';
         };
@@ -101,9 +101,9 @@ let
         default = config.security.pam.usb.enable;
         defaultText = literalExpression "config.security.pam.usb.enable";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, users listed in
-          <filename>/etc/pamusb.conf</filename> are able to log in
+          {file}`/etc/pamusb.conf` are able to log in
           with the associated USB key.
         '';
       };
@@ -112,21 +112,21 @@ let
         default = config.security.pam.enableOTPW;
         defaultText = literalExpression "config.security.pam.enableOTPW";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, the OTPW system will be used (if
-          <filename>~/.otpw</filename> exists).
+          {file}`~/.otpw` exists).
         '';
       };
 
       googleOsLoginAccountVerification = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, will use the Google OS Login PAM modules
-          (<literal>pam_oslogin_login</literal>,
-          <literal>pam_oslogin_admin</literal>) to verify possible OS Login
+          (`pam_oslogin_login`,
+          `pam_oslogin_admin`) to verify possible OS Login
           users and set sudoers configuration accordingly.
-          This only makes sense to enable for the <literal>sshd</literal> PAM
+          This only makes sense to enable for the `sshd` PAM
           service.
         '';
       };
@@ -134,19 +134,29 @@ let
       googleOsLoginAuthentication = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          If set, will use the <literal>pam_oslogin_login</literal>'s user
+        description = lib.mdDoc ''
+          If set, will use the `pam_oslogin_login`'s user
           authentication methods to authenticate users using 2FA.
-          This only makes sense to enable for the <literal>sshd</literal> PAM
+          This only makes sense to enable for the `sshd` PAM
           service.
         '';
       };
 
+      mysqlAuth = mkOption {
+        default = config.users.mysql.enable;
+        defaultText = literalExpression "config.users.mysql.enable";
+        type = types.bool;
+        description = lib.mdDoc ''
+          If set, the `pam_mysql` module will be used to
+          authenticate users against a MySQL/MariaDB database.
+        '';
+      };
+
       fprintAuth = mkOption {
         default = config.services.fprintd.enable;
         defaultText = literalExpression "config.services.fprintd.enable";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, fingerprint reader will be used (if exists and
           your fingerprints are enrolled).
         '';
@@ -156,7 +166,7 @@ let
         default = config.security.pam.oath.enable;
         defaultText = literalExpression "config.security.pam.oath.enable";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, the OATH Toolkit will be used.
         '';
       };
@@ -164,11 +174,11 @@ let
       sshAgentAuth = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, the calling user's SSH agent is used to authenticate
           against the keys in the calling user's
-          <filename>~/.ssh/authorized_keys</filename>.  This is useful
-          for <command>sudo</command> on password-less remote systems.
+          {file}`~/.ssh/authorized_keys`.  This is useful
+          for {command}`sudo` on password-less remote systems.
         '';
       };
 
@@ -176,10 +186,10 @@ let
         enable = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             If set, use the Duo Security pam module
-            <literal>pam_duo</literal> for authentication.  Requires
-            configuration of <option>security.duosec</option> options.
+            `pam_duo` for authentication.  Requires
+            configuration of {option}`security.duosec` options.
           '';
         };
       };
@@ -187,7 +197,7 @@ let
       startSession = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, the service will register a new session with
           systemd's login manager.  For local sessions, this will give
           the user access to audio devices, CD-ROM drives.  In the
@@ -199,21 +209,21 @@ let
       setEnvironment = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the service should set the environment variables
-          listed in <option>environment.sessionVariables</option>
-          using <literal>pam_env.so</literal>.
+          listed in {option}`environment.sessionVariables`
+          using `pam_env.so`.
         '';
       };
 
       setLoginUid = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Set the login uid of the process
-          (<filename>/proc/self/loginuid</filename>) for auditing
+          ({file}`/proc/self/loginuid`) for auditing
           purposes.  The login uid is only set by ‘entry points’ like
-          <command>login</command> and <command>sshd</command>, not by
-          commands like <command>sudo</command>.
+          {command}`login` and {command}`sshd`, not by
+          commands like {command}`sudo`.
         '';
       };
 
@@ -221,7 +231,7 @@ let
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable or disable TTY auditing for specified users
           '';
         };
@@ -229,7 +239,7 @@ let
         enablePattern = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             For each user matching one of comma-separated
             glob patterns, enable TTY auditing
           '';
@@ -238,7 +248,7 @@ let
         disablePattern = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             For each user matching one of comma-separated
             glob patterns, disable TTY auditing
           '';
@@ -247,7 +257,7 @@ let
         openOnly = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Set the TTY audit flag when opening the session,
             but do not restore it when closing the session.
             Using this option is necessary for some services
@@ -260,10 +270,10 @@ let
       forwardXAuth = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether X authentication keys should be passed from the
           calling user to the target user (e.g. for
-          <command>su</command>)
+          {command}`su`)
         '';
       };
 
@@ -271,21 +281,21 @@ let
         default = config.security.pam.mount.enable;
         defaultText = literalExpression "config.security.pam.mount.enable";
         type = types.bool;
-        description = ''
-          Enable PAM mount (pam_mount) system to mount fileystems on user login.
+        description = lib.mdDoc ''
+          Enable PAM mount (pam_mount) system to mount filesystems on user login.
         '';
       };
 
       allowNullPassword = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow logging into accounts that have no password
           set (i.e., have an empty password field in
-          <filename>/etc/passwd</filename> or
-          <filename>/etc/group</filename>).  This does not enable
+          {file}`/etc/passwd` or
+          {file}`/etc/group`).  This does not enable
           logging into disabled accounts (i.e., that have the password
-          field set to <literal>!</literal>).  Note that regardless of
+          field set to `!`).  Note that regardless of
           what the pam_unix documentation says, accounts with hashed
           empty passwords are always allowed to log in.
         '';
@@ -294,15 +304,15 @@ let
       nodelay = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Wheather the delay after typing a wrong password should be disabled.
+        description = lib.mdDoc ''
+          Whether the delay after typing a wrong password should be disabled.
         '';
       };
 
       requireWheel = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to permit root access only to members of group wheel.
         '';
       };
@@ -310,27 +320,25 @@ 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)`.
         '';
       };
 
       showMotd = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to show the message of the day.";
+        description = lib.mdDoc "Whether to show the message of the day.";
       };
 
       makeHomeDir = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to try to create home directories for users
-          with <literal>$HOME</literal>s pointing to nonexistent
+          with `$HOME`s pointing to nonexistent
           locations on session login.
         '';
       };
@@ -338,19 +346,19 @@ let
       updateWtmp = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to update <filename>/var/log/wtmp</filename>.";
+        description = lib.mdDoc "Whether to update {file}`/var/log/wtmp`.";
       };
 
       logFailures = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to log authentication failures in <filename>/var/log/faillog</filename>.";
+        description = lib.mdDoc "Whether to log authentication failures in {file}`/var/log/faillog`.";
       };
 
       enableAppArmor = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable support for attaching AppArmor profiles at the
           user/group level, e.g., as part of a role based access
           control scheme.
@@ -360,7 +368,7 @@ let
       enableKwallet = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, pam_wallet will attempt to automatically unlock the
           user's default KDE wallet upon login. If the user has no wallet named
           "kdewallet", or the login password does not match their wallet
@@ -370,13 +378,13 @@ let
       sssdStrictAccess = mkOption {
         default = false;
         type = types.bool;
-        description = "enforce sssd access control";
+        description = lib.mdDoc "enforce sssd access control";
       };
 
       enableGnomeKeyring = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, pam_gnome_keyring will attempt to automatically unlock the
           user's default Gnome keyring upon login. If the user login password does
           not match their keyring password, Gnome Keyring will prompt separately
@@ -384,28 +392,46 @@ 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;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             If enabled, pam_gnupg will attempt to automatically unlock the
             user's GPG keys with the login password via
-            <command>gpg-agent</command>. The keygrips of all keys to be
-            unlocked should be written to <filename>~/.pam-gnupg</filename>,
-            and can be queried with <command>gpg -K --with-keygrip</command>.
+            {command}`gpg-agent`. The keygrips of all keys to be
+            unlocked should be written to {file}`~/.pam-gnupg`,
+            and can be queried with {command}`gpg -K --with-keygrip`.
             Presetting passphrases must be enabled by adding
-            <literal>allow-preset-passphrase</literal> in
-            <filename>~/.gnupg/gpg-agent.conf</filename>.
+            `allow-preset-passphrase` in
+            {file}`~/.gnupg/gpg-agent.conf`.
           '';
         };
 
         noAutostart = mkOption {
           type = types.bool;
           default = false;
-          description = ''
-            Don't start <command>gpg-agent</command> if it is not running.
-            Useful in conjunction with starting <command>gpg-agent</command> as
+          description = lib.mdDoc ''
+            Don't start {command}`gpg-agent` if it is not running.
+            Useful in conjunction with starting {command}`gpg-agent` as
             a systemd user service.
           '';
         };
@@ -413,16 +439,16 @@ let
         storeOnly = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Don't send the password immediately after login, but store for PAM
-            <literal>session</literal>.
+            `session`.
           '';
         };
       };
 
       text = mkOption {
         type = types.nullOr types.lines;
-        description = "Contents of the PAM service file.";
+        description = lib.mdDoc "Contents of the PAM service file.";
       };
 
     };
@@ -442,25 +468,31 @@ let
         (
           ''
             # Account management.
-            account required pam_unix.so
           '' +
           optionalString use_ldap ''
             account sufficient ${pam_ldap}/lib/security/pam_ldap.so
           '' +
+          optionalString cfg.mysqlAuth ''
+            account sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
+          '' +
           optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false) ''
             account sufficient ${pkgs.sssd}/lib/security/pam_sss.so
           '' +
           optionalString (config.services.sssd.enable && cfg.sssdStrictAccess) ''
             account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so
           '' +
-          optionalString config.krb5.enable ''
+          optionalString config.security.pam.krb5.enable ''
             account sufficient ${pam_krb5}/lib/security/pam_krb5.so
           '' +
           optionalString cfg.googleOsLoginAccountVerification ''
             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
           '' +
+          # 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.
           ''
+            account required pam_unix.so
 
             # Authentication management.
           '' +
@@ -476,15 +508,19 @@ let
           optionalString cfg.logFailures ''
             auth required pam_faillock.so
           '' +
+          optionalString cfg.mysqlAuth ''
+            auth sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
+          '' +
           optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) ''
             auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=${lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles}
           '' +
           (let p11 = config.security.pam.p11; in optionalString cfg.p11Auth ''
             auth ${p11.control} ${pkgs.pam_p11}/lib/security/pam_p11.so ${pkgs.opensc}/lib/opensc-pkcs11.so
           '') +
-          (let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth ''
-            auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"} ${optionalString (u2f.appId != null) "appid=${u2f.appId}"}
-          '') +
+          (let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth (''
+              auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ''
+                + ''${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"} ${optionalString (u2f.appId != null) "appid=${u2f.appId}"} ${optionalString (u2f.origin != null) "origin=${u2f.origin}"}
+          '')) +
           optionalString cfg.usbAuth ''
             auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
           '' +
@@ -492,7 +528,7 @@ let
             auth ${ussh.control} ${pkgs.pam_ussh}/lib/security/pam_ussh.so ${optionalString (ussh.caFile != null) "ca_file=${ussh.caFile}"} ${optionalString (ussh.authorizedPrincipals != null) "authorized_principals=${ussh.authorizedPrincipals}"} ${optionalString (ussh.authorizedPrincipalsFile != null) "authorized_principals_file=${ussh.authorizedPrincipalsFile}"} ${optionalString (ussh.group != null) "group=${ussh.group}"}
           '') +
           (let oath = config.security.pam.oath; in optionalString cfg.oathAuth ''
-            auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}
+            auth requisite ${pkgs.oath-toolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}
           '') +
           (let yubi = config.security.pam.yubico; in optionalString cfg.yubicoAuth ''
             auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.challengeResponsePath != null) "chalresp_path=${yubi.challengeResponsePath}"} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}
@@ -503,24 +539,29 @@ 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 &&
             (config.security.pam.enableEcryptfs
+              || config.security.pam.enableFscrypt
               || cfg.pamMount
               || cfg.enableKwallet
               || cfg.enableGnomeKeyring
               || cfg.googleAuthenticator.enable
               || cfg.gnupg.enable
+              || cfg.failDelay.enable
               || cfg.duoSecurity.enable))
             (
               ''
-                auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
+                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.pamMount ''
                 auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
               '' +
@@ -533,6 +574,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
               '' +
@@ -552,7 +596,7 @@ let
           optionalString config.services.sssd.enable ''
             auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass
           '' +
-          optionalString config.krb5.enable ''
+          optionalString config.security.pam.krb5.enable ''
             auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
             auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
             auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
@@ -566,16 +610,22 @@ let
           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.pamMount ''
             password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
           '' +
           optionalString use_ldap ''
             password sufficient ${pam_ldap}/lib/security/pam_ldap.so
           '' +
+          optionalString cfg.mysqlAuth ''
+            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
           '' +
-          optionalString config.krb5.enable ''
+          optionalString config.security.pam.krb5.enable ''
             password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
           '' +
           optionalString cfg.enableGnomeKeyring ''
@@ -594,12 +644,12 @@ 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 cfg.makeHomeDir ''
             session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077
           '' +
@@ -609,24 +659,34 @@ 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.pamMount ''
-            session [success=1 default=ignore] ${pkgs.pam}/lib/security/pam_succeed_if.so service = systemd-user quiet
             session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
           '' +
           optionalString use_ldap ''
             session optional ${pam_ldap}/lib/security/pam_ldap.so
           '' +
+          optionalString cfg.mysqlAuth ''
+            session optional ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
+          '' +
           optionalString config.services.sssd.enable ''
             session optional ${pkgs.sssd}/lib/security/pam_sss.so
           '' +
-          optionalString config.krb5.enable ''
+          optionalString config.security.pam.krb5.enable ''
             session optional ${pam_krb5}/lib/security/pam_krb5.so
           '' +
           optionalString cfg.otpwAuth ''
             session optional ${pkgs.otpw}/lib/security/pam_otpw.so
           '' +
           optionalString cfg.startSession ''
-            session optional ${pkgs.systemd}/lib/security/pam_systemd.so
+            session optional ${config.systemd.package}/lib/security/pam_systemd.so
           '' +
           optionalString cfg.forwardXAuth ''
             session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99
@@ -634,7 +694,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) ''
@@ -673,19 +733,19 @@ let
   limitsType = with lib.types; listOf (submodule ({ ... }: {
     options = {
       domain = mkOption {
-        description = "Username, groupname, or wildcard this limit applies to";
+        description = lib.mdDoc "Username, groupname, or wildcard this limit applies to";
         example = "@wheel";
         type = str;
       };
 
       type = mkOption {
-        description = "Type of this limit";
+        description = lib.mdDoc "Type of this limit";
         type = enum [ "-" "hard" "soft" ];
         default = "-";
       };
 
       item = mkOption {
-        description = "Item this limit applies to";
+        description = lib.mdDoc "Item this limit applies to";
         type = enum [
           "core"
           "data"
@@ -709,13 +769,15 @@ let
       };
 
       value = mkOption {
-        description = "Value of this limit";
+        description = lib.mdDoc "Value of this limit";
         type = oneOf [ str int ];
       };
     };
   }));
 
-  motd = pkgs.writeText "motd" config.users.motd;
+  motd = if isNull config.users.motdFile
+         then pkgs.writeText "motd" config.users.motd
+         else config.users.motdFile;
 
   makePAMService = name: service:
     { name = "pam.d/${name}";
@@ -750,29 +812,28 @@ 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 {
       default = {};
       type = with types; attrsOf (submodule pamOpts);
       description =
-        ''
+        lib.mdDoc ''
           This option defines the PAM services.  A service typically
           corresponds to a program that uses PAM,
-          e.g. <command>login</command> or <command>passwd</command>.
+          e.g. {command}`login` or {command}`passwd`.
           Each attribute of this set defines a PAM service, with the attribute name
           defining the name of the service.
         '';
@@ -782,9 +843,9 @@ in
       type = types.str;
       default = "/var/empty";
       example =  "/etc/skel";
-      description = ''
+      description = lib.mdDoc ''
         Path to skeleton directory whose contents are copied to home
-        directories newly created by <literal>pam_mkhomedir</literal>.
+        directories newly created by `pam_mkhomedir`.
       '';
     };
 
@@ -792,44 +853,60 @@ in
       type = types.bool;
       default = false;
       description =
-        ''
+        lib.mdDoc ''
           Enable sudo logins if the user's SSH agent provides a key
-          present in <filename>~/.ssh/authorized_keys</filename>.
+          present in {file}`~/.ssh/authorized_keys`.
           This allows machines to exclusively use SSH keys instead of
           passwords.
         '';
     };
 
-    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 {
+        default = config.krb5.enable;
+        defaultText = literalExpression "config.krb5.enable";
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enables Kerberos PAM modules (`pam-krb5`,
+          `pam-ccreds`).
+
+          If set, users can authenticate with their Kerberos password.
+          This requires a valid Kerberos configuration
+          (`config.krb5.enable` should be set to
+          `true`).
+
+          Note that the Kerberos PAM modules are not necessary when using SSS
+          to handle Kerberos authentication.
+        '';
+      };
+    };
 
     security.pam.p11 = {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Enables P11 PAM (<literal>pam_p11</literal>) module.
+        description = lib.mdDoc ''
+          Enables P11 PAM (`pam_p11`) module.
 
           If set, users can log in with SSH keys and PKCS#11 tokens.
 
-          More information can be found <link
-          xlink:href="https://github.com/OpenSC/pam_p11">here</link>.
+          More information can be found [here](https://github.com/OpenSC/pam_p11).
         '';
       };
 
       control = mkOption {
         default = "sufficient";
         type = types.enum [ "required" "requisite" "sufficient" "optional" ];
-        description = ''
+        description = lib.mdDoc ''
           This option sets pam "control".
           If you want to have multi factor authentication, use "required".
           If you want to use the PKCS#11 device instead of the regular password,
           use "sufficient".
 
           Read
-          <citerefentry>
-            <refentrytitle>pam.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>
+          {manpage}`pam.conf(5)`
           for better understanding of this option.
         '';
       };
@@ -839,75 +916,84 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Enables U2F PAM (<literal>pam-u2f</literal>) module.
+        description = lib.mdDoc ''
+          Enables U2F PAM (`pam-u2f`) module.
 
           If set, users listed in
-          <filename>$XDG_CONFIG_HOME/Yubico/u2f_keys</filename> (or
-          <filename>$HOME/.config/Yubico/u2f_keys</filename> if XDG variable is
+          {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
+          {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
           not set) are able to log in with the associated U2F key. The path can
-          be changed using <option>security.pam.u2f.authFile</option> option.
+          be changed using {option}`security.pam.u2f.authFile` option.
 
           File format is:
-          <literal>username:first_keyHandle,first_public_key: second_keyHandle,second_public_key</literal>
-          This file can be generated using <command>pamu2fcfg</command> command.
+          `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
+          This file can be generated using {command}`pamu2fcfg` command.
 
-          More information can be found <link
-          xlink:href="https://developers.yubico.com/pam-u2f/">here</link>.
+          More information can be found [here](https://developers.yubico.com/pam-u2f/).
         '';
       };
 
       authFile = mkOption {
         default = null;
         type = with types; nullOr path;
-        description = ''
-          By default <literal>pam-u2f</literal> module reads the keys from
-          <filename>$XDG_CONFIG_HOME/Yubico/u2f_keys</filename> (or
-          <filename>$HOME/.config/Yubico/u2f_keys</filename> if XDG variable is
+        description = lib.mdDoc ''
+          By default `pam-u2f` module reads the keys from
+          {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
+          {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
           not set).
 
           If you want to change auth file locations or centralize database (for
-          example use <filename>/etc/u2f-mappings</filename>) you can set this
+          example use {file}`/etc/u2f-mappings`) you can set this
           option.
 
           File format is:
-          <literal>username:first_keyHandle,first_public_key: second_keyHandle,second_public_key</literal>
-          This file can be generated using <command>pamu2fcfg</command> command.
+          `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
+          This file can be generated using {command}`pamu2fcfg` command.
 
-          More information can be found <link
-          xlink:href="https://developers.yubico.com/pam-u2f/">here</link>.
+          More information can be found [here](https://developers.yubico.com/pam-u2f/).
         '';
       };
 
       appId = mkOption {
         default = null;
         type = with types; nullOr str;
-        description = ''
-            By default <literal>pam-u2f</literal> module sets the application
-            ID to <literal>pam://$HOSTNAME</literal>.
+        description = lib.mdDoc ''
+            By default `pam-u2f` module sets the application
+            ID to `pam://$HOSTNAME`.
 
-            When using <command>pamu2fcfg</command>, you can specify your
-            application ID with the <literal>-i</literal> flag.
+            When using {command}`pamu2fcfg`, you can specify your
+            application ID with the `-i` flag.
 
-            More information can be found <link
-            xlink:href="https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html">
-            here</link>
+            More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
+        '';
+      };
+
+      origin = mkOption {
+        default = null;
+        type = with types; nullOr str;
+        description = lib.mdDoc ''
+            By default `pam-u2f` module sets the origin
+            to `pam://$HOSTNAME`.
+            Setting origin to an host independent value will allow you to
+            reuse credentials across machines
+
+            When using {command}`pamu2fcfg`, you can specify your
+            application ID with the `-o` flag.
+
+            More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
         '';
       };
 
       control = mkOption {
         default = "sufficient";
         type = types.enum [ "required" "requisite" "sufficient" "optional" ];
-        description = ''
+        description = lib.mdDoc ''
           This option sets pam "control".
           If you want to have multi factor authentication, use "required".
           If you want to use U2F device instead of regular password, use "sufficient".
 
           Read
-          <citerefentry>
-            <refentrytitle>pam.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>
+          {manpage}`pam.conf(5)`
           for better understanding of this option.
         '';
       };
@@ -915,7 +1001,7 @@ in
       debug = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Debug output to stderr.
         '';
       };
@@ -923,7 +1009,7 @@ in
       interactive = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Set to prompt a message and wait before testing the presence of a U2F device.
           Recommended if your device doesn’t have a tactile trigger.
         '';
@@ -932,12 +1018,12 @@ in
       cue = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          By default <literal>pam-u2f</literal> module does not inform user
+        description = lib.mdDoc ''
+          By default `pam-u2f` module does not inform user
           that he needs to use the u2f device, it just waits without a prompt.
 
-          If you set this option to <literal>true</literal>,
-          <literal>cue</literal> option is added to <literal>pam-u2f</literal>
+          If you set this option to `true`,
+          `cue` option is added to `pam-u2f`
           module and reminder message will be displayed.
         '';
       };
@@ -947,29 +1033,28 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Enables Uber's USSH PAM (<literal>pam-ussh</literal>) module.
+        description = lib.mdDoc ''
+          Enables Uber's USSH PAM (`pam-ussh`) module.
 
-          This is similar to <literal>pam-ssh-agent</literal>, except that
+          This is similar to `pam-ssh-agent`, except that
           the presence of a CA-signed SSH key with a valid principal is checked
           instead.
 
           Note that this module must both be enabled using this option and on a
-          per-PAM-service level as well (using <literal>usshAuth</literal>).
+          per-PAM-service level as well (using `usshAuth`).
 
-          More information can be found <link
-          xlink:href="https://github.com/uber/pam-ussh">here</link>.
+          More information can be found [here](https://github.com/uber/pam-ussh).
         '';
       };
 
       caFile = mkOption {
         default = null;
         type = with types; nullOr path;
-        description = ''
-          By default <literal>pam-ussh</literal> reads the trusted user CA keys
-          from <filename>/etc/ssh/trusted_user_ca</filename>.
+        description = lib.mdDoc ''
+          By default `pam-ussh` reads the trusted user CA keys
+          from {file}`/etc/ssh/trusted_user_ca`.
 
-          This should be set the same as your <literal>TrustedUserCAKeys</literal>
+          This should be set the same as your `TrustedUserCAKeys`
           option for sshd.
         '';
       };
@@ -977,38 +1062,38 @@ in
       authorizedPrincipals = mkOption {
         default = null;
         type = with types; nullOr commas;
-        description = ''
+        description = lib.mdDoc ''
           Comma-separated list of authorized principals to permit; if the user
           presents a certificate with one of these principals, then they will be
           authorized.
 
-          Note that <literal>pam-ussh</literal> also requires that the certificate
+          Note that `pam-ussh` also requires that the certificate
           contain a principal matching the user's username. The principals from
           this list are in addition to those principals.
 
-          Mutually exclusive with <literal>authorizedPrincipalsFile</literal>.
+          Mutually exclusive with `authorizedPrincipalsFile`.
         '';
       };
 
       authorizedPrincipalsFile = mkOption {
         default = null;
         type = with types; nullOr path;
-        description = ''
+        description = lib.mdDoc ''
           Path to a list of principals; if the user presents a certificate with
           one of these principals, then they will be authorized.
 
-          Note that <literal>pam-ussh</literal> also requires that the certificate
+          Note that `pam-ussh` also requires that the certificate
           contain a principal matching the user's username. The principals from
           this file are in addition to those principals.
 
-          Mutually exclusive with <literal>authorizedPrincipals</literal>.
+          Mutually exclusive with `authorizedPrincipals`.
         '';
       };
 
       group = mkOption {
         default = null;
         type = with types; nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           If set, then the authenticating user must be a member of this group
           to use this module.
         '';
@@ -1017,17 +1102,14 @@ in
       control = mkOption {
         default = "sufficient";
         type = types.enum [ "required" "requisite" "sufficient" "optional" ];
-        description = ''
+        description = lib.mdDoc ''
           This option sets pam "control".
           If you want to have multi factor authentication, use "required".
           If you want to use the SSH certificate instead of the regular password,
           use "sufficient".
 
           Read
-          <citerefentry>
-            <refentrytitle>pam.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>
+          {manpage}`pam.conf(5)`
           for better understanding of this option.
         '';
       };
@@ -1037,52 +1119,48 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Enables Yubico PAM (<literal>yubico-pam</literal>) module.
+        description = lib.mdDoc ''
+          Enables Yubico PAM (`yubico-pam`) module.
 
           If set, users listed in
-          <filename>~/.yubico/authorized_yubikeys</filename>
+          {file}`~/.yubico/authorized_yubikeys`
           are able to log in with the associated Yubikey tokens.
 
           The file must have only one line:
-          <literal>username:yubikey_token_id1:yubikey_token_id2</literal>
-          More information can be found <link
-          xlink:href="https://developers.yubico.com/yubico-pam/">here</link>.
+          `username:yubikey_token_id1:yubikey_token_id2`
+          More information can be found [here](https://developers.yubico.com/yubico-pam/).
         '';
       };
       control = mkOption {
         default = "sufficient";
         type = types.enum [ "required" "requisite" "sufficient" "optional" ];
-        description = ''
+        description = lib.mdDoc ''
           This option sets pam "control".
           If you want to have multi factor authentication, use "required".
           If you want to use Yubikey instead of regular password, use "sufficient".
 
           Read
-          <citerefentry>
-            <refentrytitle>pam.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>
+          {manpage}`pam.conf(5)`
           for better understanding of this option.
         '';
       };
       id = mkOption {
         example = "42";
         type = types.str;
-        description = "client id";
+        description = lib.mdDoc "client id";
       };
 
       debug = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Debug output to stderr.
         '';
       };
       mode = mkOption {
         default = "client";
         type = types.enum [ "client" "challenge-response" ];
-        description = ''
+        description = lib.mdDoc ''
           Mode of operation.
 
           Use "client" for online validation with a YubiKey validation service such as
@@ -1092,47 +1170,68 @@ in
           Challenge-Response configurations. See the man-page ykpamcfg(1) for further
           details on how to configure offline Challenge-Response validation.
 
-          More information can be found <link
-          xlink:href="https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html">here</link>.
+          More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
         '';
       };
       challengeResponsePath = mkOption {
         default = null;
         type = types.nullOr types.path;
-        description = ''
+        description = lib.mdDoc ''
           If not null, set the path used by yubico pam module where the challenge expected response is stored.
 
-          More information can be found <link
-          xlink:href="https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html">here</link>.
+          More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
         '';
       };
     };
 
-    security.pam.enableEcryptfs = mkEnableOption "eCryptfs PAM module (mounting ecryptfs home directory on login)";
+    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;
       example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178.";
       type = types.nullOr types.lines;
-      description = "Message of the day shown to users when they log 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 = isNull config.users.motd || isNull config.users.motdFile;
+        message = ''
+          Only one of users.motd and users.motdFile can be set.
+        '';
+      }
+    ];
 
     environment.systemPackages =
       # Include the PAM modules in the system path mostly for the manpages.
       [ pkgs.pam ]
       ++ optional config.users.ldap.enable pam_ldap
       ++ optional config.services.sssd.enable pkgs.sssd
-      ++ optionals config.krb5.enable [pam_krb5 pam_ccreds]
+      ++ optionals config.security.pam.krb5.enable [pam_krb5 pam_ccreds]
       ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
-      ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ]
+      ++ optionals config.security.pam.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" ];
@@ -1174,6 +1273,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
@@ -1193,7 +1295,7 @@ in
       optionalString config.services.sssd.enable ''
         mr ${pkgs.sssd}/lib/security/pam_sss.so,
       '' +
-      optionalString config.krb5.enable ''
+      optionalString config.security.pam.krb5.enable ''
         mr ${pam_krb5}/lib/security/pam_krb5.so,
         mr ${pam_ccreds}/lib/security/pam_ccreds.so,
       '' +
@@ -1221,7 +1323,10 @@ in
         mr ${pkgs.pam_ussh}/lib/security/pam_ussh.so,
       '' +
       optionalString (isEnabled (cfg: cfg.oathAuth)) ''
-        "mr ${pkgs.oathToolkit}/lib/security/pam_oath.so,
+        "mr ${pkgs.oath-toolkit}/lib/security/pam_oath.so,
+      '' +
+      optionalString (isEnabled (cfg: cfg.mysqlAuth)) ''
+        mr ${pkgs.pam_mysql}/lib/security/pam_mysql.so,
       '' +
       optionalString (isEnabled (cfg: cfg.yubicoAuth)) ''
         mr ${pkgs.yubico-pam}/lib/security/pam_yubico.so,
@@ -1235,14 +1340,17 @@ 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 ${pkgs.systemd}/lib/security/pam_systemd.so,
+        mr ${config.systemd.package}/lib/security/pam_systemd.so,
       '' +
       optionalString (isEnabled (cfg: cfg.enableAppArmor)
                      && config.security.apparmor.enable) ''
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index 1d0efee8ca8e..481f1f3d38eb 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -23,18 +23,17 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Enable PAM mount system to mount fileystems on user login.
+        description = lib.mdDoc ''
+          Enable PAM mount system to mount filesystems on user login.
         '';
       };
 
       extraVolumes = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of volume definitions for pam_mount.
-          For more information, visit <link
-          xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />.
+          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
@@ -42,7 +41,7 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.bindfs ]";
-        description = ''
+        description = lib.mdDoc ''
           Additional programs to include in the search path of pam_mount.
           Useful for example if you want to use some FUSE filesystems like bindfs.
         '';
@@ -54,7 +53,7 @@ in
         example = literalExpression ''
           [ "nodev" "nosuid" "force-user=%(USER)" "gid=%(USERGID)" "perms=0700" "chmod-deny" "chown-deny" "chgrp-deny" ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Global mount options that apply to every FUSE volume.
           You can define volume-specific options in the volume definitions.
         '';
@@ -64,29 +63,27 @@ in
         type = types.int;
         default = 0;
         example = 1;
-        description = ''
+        description = lib.mdDoc ''
           Sets the Debug-Level. 0 disables debugging, 1 enables pam_mount tracing,
           and 2 additionally enables tracing in mount.crypt. The default is 0.
-          For more information, visit <link
-          xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />.
+          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
       logoutWait = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Amount of microseconds to wait until killing remaining processes after
           final logout.
-          For more information, visit <link
-          xlink:href="http://pam-mount.sourceforge.net/pam_mount.conf.5.html" />.
+          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
       logoutHup = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Kill remaining processes after logout by sending a SIGHUP.
         '';
       };
@@ -94,7 +91,7 @@ in
       logoutTerm = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Kill remaining processes after logout by sending a SIGTERM.
         '';
       };
@@ -102,7 +99,7 @@ in
       logoutKill = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Kill remaining processes after logout by sending a SIGKILL.
         '';
       };
@@ -110,7 +107,7 @@ in
       createMountPoints = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Create mountpoints for volumes if they do not exist.
         '';
       };
@@ -118,7 +115,7 @@ in
       removeCreatedMountPoints = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Remove mountpoints created by pam_mount after logout. This
           only affects mountpoints that have been created by pam_mount
           in the same session.
diff --git a/nixos/modules/security/pam_usb.nix b/nixos/modules/security/pam_usb.nix
index 51d81e823f86..4275c26c6bda 100644
--- a/nixos/modules/security/pam_usb.nix
+++ b/nixos/modules/security/pam_usb.nix
@@ -17,10 +17,9 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable USB login for all login systems that support it.  For
-          more information, visit <link
-          xlink:href="https://github.com/aluzzardi/pam_usb/wiki/Getting-Started#setting-up-devices-and-users" />.
+          more information, visit <https://github.com/aluzzardi/pam_usb/wiki/Getting-Started#setting-up-devices-and-users>.
         '';
       };
 
diff --git a/nixos/modules/security/please.nix b/nixos/modules/security/please.nix
new file mode 100644
index 000000000000..88bb9cba2bfc
--- /dev/null
+++ b/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/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index 1ba149745c65..f33898578b81 100644
--- a/nixos/modules/security/polkit.nix
+++ b/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);
           });
 
@@ -29,7 +32,7 @@ in
             if (subject.local) return "yes";
           });
         '';
-      description =
+      description = lib.mdDoc
         ''
           Any polkit rules to be added to config (in JavaScript ;-). See:
           http://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules
@@ -40,12 +43,12 @@ in
       type = types.listOf types.str;
       default = [ "unix-group:wheel" ];
       example = [ "unix-user:alice" "unix-group:admin" ];
-      description =
+      description = lib.mdDoc
         ''
           Specifies which users are considered “administrators”, for those
           actions that require the user to authenticate as an
-          administrator (i.e. have an <literal>auth_admin</literal>
-          value).  By default, this is all users in the <literal>wheel</literal> group.
+          administrator (i.e. have an `auth_admin`
+          value).  By default, this is all users in the `wheel` group.
         '';
     };
 
@@ -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/nixos/modules/security/rtkit.nix b/nixos/modules/security/rtkit.nix
index ad8746808e85..0f58b4dce84a 100644
--- a/nixos/modules/security/rtkit.nix
+++ b/nixos/modules/security/rtkit.nix
@@ -12,7 +12,7 @@ with lib;
     security.rtkit.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the RealtimeKit system service, which hands
         out realtime scheduling priority to user processes on
         demand. For example, the PulseAudio server uses this to
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index 4bf239fca8f9..296b61fd703b 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -36,8 +36,8 @@ in
       type = types.bool;
       default = true;
       description =
-        ''
-          Whether to enable the <command>sudo</command> command, which
+        lib.mdDoc ''
+          Whether to enable the {command}`sudo` command, which
           allows non-root users to execute commands as root.
         '';
     };
@@ -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`.
       '';
     };
@@ -55,19 +55,19 @@ in
       type = types.bool;
       default = true;
       description =
-        ''
-          Whether users of the <code>wheel</code> group must
-          provide a password to run commands as super user via <command>sudo</command>.
+        lib.mdDoc ''
+          Whether users of the `wheel` group must
+          provide a password to run commands as super user via {command}`sudo`.
         '';
       };
 
     security.sudo.execWheelOnly = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Only allow members of the <code>wheel</code> group to execute sudo by
+      description = lib.mdDoc ''
+        Only allow members of the `wheel` group to execute sudo by
         setting the executable's permissions accordingly.
-        This prevents users that are not members of <code>wheel</code> from
+        This prevents users that are not members of `wheel` from
         exploiting vulnerabilities in sudo such as CVE-2021-3156.
       '';
     };
@@ -77,15 +77,15 @@ in
       # Note: if syntax errors are detected in this file, the NixOS
       # configuration will fail to build.
       description =
-        ''
+        lib.mdDoc ''
           This string contains the contents of the
-          <filename>sudoers</filename> file.
+          {file}`sudoers` file.
         '';
     };
 
     security.sudo.extraRules = mkOption {
-      description = ''
-        Define specific rules to be in the <filename>sudoers</filename> file.
+      description = lib.mdDoc ''
+        Define specific rules to be in the {file}`sudoers` file.
         More specific rules should come after more general ones in order to
         yield the expected behavior. You can use mkBefore/mkAfter to ensure
         this is the case when configuration options are merged.
@@ -114,7 +114,7 @@ in
         options = {
           users = mkOption {
             type = with types; listOf (either str int);
-            description = ''
+            description = lib.mdDoc ''
               The usernames / UIDs this rule should apply for.
             '';
             default = [];
@@ -122,7 +122,7 @@ in
 
           groups = mkOption {
             type = with types; listOf (either str int);
-            description = ''
+            description = lib.mdDoc ''
               The groups / GIDs this rule should apply for.
             '';
             default = [];
@@ -131,7 +131,7 @@ in
           host = mkOption {
             type = types.str;
             default = "ALL";
-            description = ''
+            description = lib.mdDoc ''
               For what host this rule should apply.
             '';
           };
@@ -139,17 +139,17 @@ in
           runAs = mkOption {
             type = with types; str;
             default = "ALL:ALL";
-            description = ''
+            description = lib.mdDoc ''
               Under which user/group the specified command is allowed to run.
 
-              A user can be specified using just the username: <code>"foo"</code>.
-              It is also possible to specify a user/group combination using <code>"foo:bar"</code>
-              or to only allow running as a specific group with <code>":bar"</code>.
+              A user can be specified using just the username: `"foo"`.
+              It is also possible to specify a user/group combination using `"foo:bar"`
+              or to only allow running as a specific group with `":bar"`.
             '';
           };
 
           commands = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               The commands for which the rule should apply.
             '';
             type = with types; listOf (either str (submodule {
@@ -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 <code>""</code> 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 = [];
                 };
@@ -182,8 +182,8 @@ in
     security.sudo.extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
-        Extra configuration text appended to <filename>sudoers</filename>.
+      description = lib.mdDoc ''
+        Extra configuration text appended to {file}`sudoers`.
       '';
     };
   };
diff --git a/nixos/modules/security/systemd-confinement.nix b/nixos/modules/security/systemd-confinement.nix
index f3a2de3bf87a..be04741f4d06 100644
--- a/nixos/modules/security/systemd-confinement.nix
+++ b/nixos/modules/security/systemd-confinement.nix
@@ -10,28 +10,27 @@ in {
       options.confinement.enable = lib.mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If set, all the required runtime store paths for this service are
-          bind-mounted into a <literal>tmpfs</literal>-based <citerefentry>
-            <refentrytitle>chroot</refentrytitle>
-            <manvolnum>2</manvolnum>
-          </citerefentry>.
+          bind-mounted into a `tmpfs`-based
+          {manpage}`chroot(2)`.
         '';
       };
 
       options.confinement.fullUnit = lib.mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to include the full closure of the systemd unit file into the
           chroot, instead of just the dependencies for the executables.
 
-          <warning><para>While it may be tempting to just enable this option to
+          ::: {.warning}
+          While it may be tempting to just enable this option to
           make things work quickly, please be aware that this might add paths
           to the closure of the chroot that you didn't anticipate. It's better
-          to use <option>confinement.packages</option> to <emphasis
-          role="strong">explicitly</emphasis> add additional store paths to the
-          chroot.</para></warning>
+          to use {option}`confinement.packages` to **explicitly** add additional store paths to the
+          chroot.
+          :::
         '';
       };
 
@@ -39,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 [
@@ -48,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.
+          :::
         '';
       };
 
@@ -62,38 +63,33 @@ in {
         default = toplevelConfig.environment.binsh;
         defaultText = lib.literalExpression "config.environment.binsh";
         example = lib.literalExpression ''"''${pkgs.dash}/bin/dash"'';
-        description = ''
-          The program to make available as <filename>/bin/sh</filename> inside
-          the chroot. If this is set to <literal>null</literal>, no
-          <filename>/bin/sh</filename> is provided at all.
+        description = lib.mdDoc ''
+          The program to make available as {file}`/bin/sh` inside
+          the chroot. If this is set to `null`, no
+          {file}`/bin/sh` is provided at all.
 
           This is useful for some applications, which for example use the
-          <citerefentry>
-            <refentrytitle>system</refentrytitle>
-            <manvolnum>3</manvolnum>
-          </citerefentry> library function to execute commands.
+          {manpage}`system(3)` library function to execute commands.
         '';
       };
 
       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
-          system name space is set up along with the call to <citerefentry>
-            <refentrytitle>chroot</refentrytitle>
-            <manvolnum>2</manvolnum>
-          </citerefentry>.
+          If this is set to `chroot-only`, only the file
+          system name space is set up along with the call to
+          {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.
+          :::
         '';
       };
 
diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix
index be85fd246e3c..5a023cec48ee 100644
--- a/nixos/modules/security/tpm2.nix
+++ b/nixos/modules/security/tpm2.nix
@@ -17,10 +17,10 @@ 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 = ''
+      description = lib.mdDoc ''
         Name of the tpm device-owner and service user, set if applyUdevRules is
         set.
       '';
@@ -30,7 +30,7 @@ in {
     };
 
     tssGroup = lib.mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Group of the tpm kernel resource manager (tpmrm) device-group, set if
         applyUdevRules is set.
       '';
@@ -39,7 +39,7 @@ in {
     };
 
     applyUdevRules = lib.mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to make the /dev/tpm[0-9] devices accessible by the tssUser, or
         the /dev/tpmrm[0-9] by tssGroup respectively
       '';
@@ -48,12 +48,12 @@ in {
     };
 
     abrmd = {
-      enable = lib.mkEnableOption ''
+      enable = lib.mkEnableOption (lib.mdDoc ''
         Trusted Platform 2 userspace resource manager daemon
-      '';
+      '');
 
       package = lib.mkOption {
-        description = "tpm2-abrmd package to use";
+        description = lib.mdDoc "tpm2-abrmd package to use";
         type = lib.types.package;
         default = pkgs.tpm2-abrmd;
         defaultText = lib.literalExpression "pkgs.tpm2-abrmd";
@@ -61,13 +61,13 @@ 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 = "tpm2-pkcs11 package to use";
+        description = lib.mdDoc "tpm2-pkcs11 package to use";
         type = lib.types.package;
         default = pkgs.tpm2-pkcs11;
         defaultText = lib.literalExpression "pkgs.tpm2-pkcs11";
@@ -76,28 +76,18 @@ 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;
       };
 
       interface = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The name of the TPM command transmission interface (TCTI) library to
           use.
         '';
@@ -106,24 +96,24 @@ in {
       };
 
       deviceConf = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Configuration part of the device TCTI, e.g. the path to the TPM device.
           Applies if interface is set to "device".
           The format is specified in the
-          <link xlink:href="https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options">
-          tpm2-tools repository</link>.
+          [
+          tpm2-tools repository](https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options).
         '';
         type = lib.types.str;
         default = "/dev/tpmrm0";
       };
 
       tabrmdConf = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Configuration part of the tabrmd TCTI, like the D-Bus bus name.
           Applies if interface is set to "tabrmd".
           The format is specified in the
-          <link xlink:href="https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options">
-          tpm2-tools repository</link>.
+          [
+          tpm2-tools repository](https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options).
         '';
         type = lib.types.str;
         default = "bus_name=com.intel.tss2.Tabrmd";
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index e63f19010de8..4b62abd658a4 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -22,63 +22,61 @@ let
   wrapperType = lib.types.submodule ({ name, config, ... }: {
     options.source = lib.mkOption
       { type = lib.types.path;
-        description = "The absolute path to the program to be wrapped.";
+        description = lib.mdDoc "The absolute path to the program to be wrapped.";
       };
     options.program = lib.mkOption
       { type = with lib.types; nullOr str;
         default = name;
-        description = ''
+        description = lib.mdDoc ''
           The name of the wrapper program. Defaults to the attribute name.
         '';
       };
     options.owner = lib.mkOption
       { type = lib.types.str;
-        description = "The owner of the wrapper program.";
+        description = lib.mdDoc "The owner of the wrapper program.";
       };
     options.group = lib.mkOption
       { type = lib.types.str;
-        description = "The group of the wrapper program.";
+        description = lib.mdDoc "The group of the wrapper program.";
       };
     options.permissions = lib.mkOption
       { type = fileModeType;
         default  = "u+rx,g+x,o+x";
         example = "a+rx";
-        description = ''
+        description = lib.mdDoc ''
           The permissions of the wrapper program. The format is that of a
-          symbolic or numeric file mode understood by <command>chmod</command>.
+          symbolic or numeric file mode understood by {command}`chmod`.
         '';
       };
     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
       { type = lib.types.bool;
         default = false;
-        description = "Whether to add the setuid bit the wrapper program.";
+        description = lib.mdDoc "Whether to add the setuid bit the wrapper program.";
       };
     options.setgid = lib.mkOption
       { type = lib.types.bool;
         default = false;
-        description = "Whether to add the setgid bit the wrapper program.";
+        description = lib.mdDoc "Whether to add the setgid bit the wrapper program.";
       };
   });
 
@@ -98,7 +96,7 @@ let
 
       # Prevent races
       chmod 0000 "$wrapperDir/${program}"
-      chown ${owner}.${group} "$wrapperDir/${program}"
+      chown ${owner}:${group} "$wrapperDir/${program}"
 
       # Set desired capabilities on the file plus cap_setpcap so
       # the wrapper program can elevate the capabilities set on
@@ -126,7 +124,7 @@ let
 
       # Prevent races
       chmod 0000 "$wrapperDir/${program}"
-      chown ${owner}.${group} "$wrapperDir/${program}"
+      chown ${owner}:${group} "$wrapperDir/${program}"
 
       chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}"
     '';
@@ -179,12 +177,22 @@ in
               };
           }
         '';
-      description = ''
+      description = lib.mdDoc ''
         This option effectively allows adding setuid/setgid bits, capabilities,
         changing file ownership and permissions of a program without directly
         modifying it. This works by creating a wrapper program under the
-        <option>security.wrapperDir</option> directory, which is then added to
-        the shell <literal>PATH</literal>.
+        {option}`security.wrapperDir` directory, which is then added to
+        the shell `PATH`.
+      '';
+    };
+
+    security.wrapperDirSize = lib.mkOption {
+      default = "50%";
+      example = "10G";
+      type = lib.types.str;
+      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.
       '';
     };
 
@@ -192,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.
       '';
     };
   };
@@ -230,7 +238,7 @@ in
 
     boot.specialFileSystems.${parentWrapperDir} = {
       fsType = "tmpfs";
-      options = [ "nodev" "mode=755" ];
+      options = [ "nodev" "mode=755" "size=${config.security.wrapperDirSize}" ];
     };
 
     # Make sure our wrapperDir exports to the PATH env variable when
diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c
index 529669facda8..a21ec500208d 100644
--- a/nixos/modules/security/wrappers/wrapper.c
+++ b/nixos/modules/security/wrappers/wrapper.c
@@ -2,12 +2,12 @@
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
+#include <stdnoreturn.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/xattr.h>
 #include <fcntl.h>
 #include <dirent.h>
-#include <assert.h>
 #include <errno.h>
 #include <linux/capability.h>
 #include <sys/prctl.h>
@@ -16,10 +16,7 @@
 #include <syscall.h>
 #include <byteswap.h>
 
-// Make sure assertions are not compiled out, we use them to codify
-// invariants about this program and we want it to fail fast and
-// loudly if they are violated.
-#undef NDEBUG
+#define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
 
 extern char **environ;
 
@@ -38,6 +35,12 @@ static char *wrapper_debug = "WRAPPER_DEBUG";
 #define LE32_TO_H(x) (x)
 #endif
 
+static noreturn void assert_failure(const char *assertion) {
+    fprintf(stderr, "Assertion `%s` in NixOS's wrapper.c failed.\n", assertion);
+    fflush(stderr);
+    abort();
+}
+
 int get_last_cap(unsigned *last_cap) {
     FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r");
     if (file == NULL) {
@@ -167,6 +170,7 @@ int readlink_malloc(const char *p, char **ret) {
 }
 
 int main(int argc, char **argv) {
+    ASSERT(argc >= 1);
     char *self_path = NULL;
     int self_path_size = readlink_malloc("/proc/self/exe", &self_path);
     if (self_path_size < 0) {
@@ -181,36 +185,36 @@ int main(int argc, char **argv) {
     int len = strlen(wrapper_dir);
     if (len > 0 && '/' == wrapper_dir[len - 1])
       --len;
-    assert(!strncmp(self_path, wrapper_dir, len));
-    assert('/' == wrapper_dir[0]);
-    assert('/' == self_path[len]);
+    ASSERT(!strncmp(self_path, wrapper_dir, len));
+    ASSERT('/' == wrapper_dir[0]);
+    ASSERT('/' == self_path[len]);
 
     // Make *really* *really* sure that we were executed as
     // `self_path', and not, say, as some other setuid program. That
     // is, our effective uid/gid should match the uid/gid of
     // `self_path'.
     struct stat st;
-    assert(lstat(self_path, &st) != -1);
+    ASSERT(lstat(self_path, &st) != -1);
 
-    assert(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
-    assert(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
+    ASSERT(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
+    ASSERT(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
 
     // And, of course, we shouldn't be writable.
-    assert(!(st.st_mode & (S_IWGRP | S_IWOTH)));
+    ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));
 
     // Read the path of the real (wrapped) program from <self>.real.
     char real_fn[PATH_MAX + 10];
     int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path);
-    assert(real_fn_size < sizeof(real_fn));
+    ASSERT(real_fn_size < sizeof(real_fn));
 
     int fd_self = open(real_fn, O_RDONLY);
-    assert(fd_self != -1);
+    ASSERT(fd_self != -1);
 
     char source_prog[PATH_MAX];
     len = read(fd_self, source_prog, PATH_MAX);
-    assert(len != -1);
-    assert(len < sizeof(source_prog));
-    assert(len > 0);
+    ASSERT(len != -1);
+    ASSERT(len < sizeof(source_prog));
+    ASSERT(len > 0);
     source_prog[len] = 0;
 
     close(fd_self);
diff --git a/nixos/modules/services/admin/meshcentral.nix b/nixos/modules/services/admin/meshcentral.nix
index 92762d2037c3..22f31e952622 100644
--- a/nixos/modules/services/admin/meshcentral.nix
+++ b/nixos/modules/services/admin/meshcentral.nix
@@ -5,23 +5,21 @@ 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 = "MeshCentral package to use. Replacing this may be necessary to add dependencies for extra functionality.";
+      description = lib.mdDoc "MeshCentral package to use. Replacing this may be necessary to add dependencies for extra functionality.";
       type = types.package;
       default = pkgs.meshcentral;
       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/nixos/modules/services/admin/oxidized.nix b/nixos/modules/services/admin/oxidized.nix
index 49ea3ced76a4..56f33031498a 100644
--- a/nixos/modules/services/admin/oxidized.nix
+++ b/nixos/modules/services/admin/oxidized.nix
@@ -7,12 +7,12 @@ 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;
       default = "oxidized";
-      description = ''
+      description = lib.mdDoc ''
         User under which the oxidized service runs.
       '';
     };
@@ -20,7 +20,7 @@ in
     group = mkOption {
       type = types.str;
       default = "oxidized";
-      description = ''
+      description = lib.mdDoc ''
         Group under which the oxidized service runs.
       '';
     };
@@ -28,7 +28,7 @@ in
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/oxidized";
-      description = "State directory for the oxidized service.";
+      description = lib.mdDoc "State directory for the oxidized service.";
     };
 
     configFile = mkOption {
@@ -62,7 +62,7 @@ in
           # ... additional config
         ''';
       '';
-      description = ''
+      description = lib.mdDoc ''
         Path to the oxidized configuration file.
       '';
     };
@@ -76,7 +76,7 @@ in
           # ... additional hosts
         '''
       '';
-      description = ''
+      description = lib.mdDoc ''
         Path to the file/database which contains the targets for oxidized.
       '';
     };
diff --git a/nixos/modules/services/admin/pgadmin.nix b/nixos/modules/services/admin/pgadmin.nix
index 80b681454104..390c80d1a2d4 100644
--- a/nixos/modules/services/admin/pgadmin.nix
+++ b/nixos/modules/services/admin/pgadmin.nix
@@ -28,36 +28,85 @@ let
 in
 {
   options.services.pgadmin = {
-    enable = mkEnableOption "PostgreSQL Admin 4";
+    enable = mkEnableOption (lib.mdDoc "PostgreSQL Admin 4");
 
     port = mkOption {
-      description = "Port for pgadmin4 to run on";
+      description = lib.mdDoc "Port for pgadmin4 to run on";
       type = types.port;
       default = 5050;
     };
 
     initialEmail = mkOption {
-      description = "Initial email for the pgAdmin account.";
+      description = lib.mdDoc "Initial email for the pgAdmin account";
       type = types.str;
     };
 
     initialPasswordFile = mkOption {
-      description = ''
+      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 = ''
+      description = lib.mdDoc ''
         Settings for pgadmin4.
-        <link xlink:href="https://www.pgadmin.org/docs/pgadmin4/development/config_py.html">Documentation</link>.
+        [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/nixos/modules/services/admin/salt/master.nix b/nixos/modules/services/admin/salt/master.nix
index a3069c81c19a..4346022970e1 100644
--- a/nixos/modules/services/admin/salt/master.nix
+++ b/nixos/modules/services/admin/salt/master.nix
@@ -20,11 +20,11 @@ in
 {
   options = {
     services.salt.master = {
-      enable = mkEnableOption "Salt master service";
+      enable = mkEnableOption (lib.mdDoc "Salt master service");
       configuration = mkOption {
         type = types.attrs;
         default = {};
-        description = "Salt master configuration as Nix attribute set.";
+        description = lib.mdDoc "Salt master configuration as Nix attribute set.";
       };
     };
   };
diff --git a/nixos/modules/services/admin/salt/minion.nix b/nixos/modules/services/admin/salt/minion.nix
index ac124c570d8d..3ae02a4cc5d5 100644
--- a/nixos/modules/services/admin/salt/minion.nix
+++ b/nixos/modules/services/admin/salt/minion.nix
@@ -21,13 +21,13 @@ in
 {
   options = {
     services.salt.minion = {
-      enable = mkEnableOption "Salt minion service";
+      enable = mkEnableOption (lib.mdDoc "Salt minion service");
       configuration = mkOption {
         type = types.attrs;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Salt minion configuration as Nix attribute set.
-          See <link xlink:href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html"/>
+          See <https://docs.saltstack.com/en/latest/ref/configuration/minion.html>
           for details.
         '';
       };
diff --git a/nixos/modules/services/amqp/activemq/default.nix b/nixos/modules/services/amqp/activemq/default.nix
index 47669b05aa91..bd37fe3b5574 100644
--- a/nixos/modules/services/amqp/activemq/default.nix
+++ b/nixos/modules/services/amqp/activemq/default.nix
@@ -27,7 +27,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the Apache ActiveMQ message broker service.
         '';
       };
@@ -35,7 +35,7 @@ in {
         default = "${activemq}/conf";
         defaultText = literalExpression ''"''${pkgs.activemq}/conf"'';
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The base directory for ActiveMQ's configuration.
           By default, this directory is searched for a file named activemq.xml,
           which should contain the configuration for the broker service.
@@ -44,21 +44,21 @@ in {
       configurationURI = mkOption {
         type = types.str;
         default = "xbean:activemq.xml";
-        description = ''
+        description = lib.mdDoc ''
           The URI that is passed along to the BrokerFactory to
           set up the configuration of the ActiveMQ broker service.
           You should not need to change this. For custom configuration,
-          set the <literal>configurationDir</literal> instead, and create
+          set the `configurationDir` instead, and create
           an activemq.xml configuration file in it.
         '';
       };
       baseDir = mkOption {
         type = types.str;
         default = "/var/activemq";
-        description = ''
+        description = lib.mdDoc ''
           The base directory where ActiveMQ stores its persistent data and logs.
           This will be overridden if you set "activemq.base" and "activemq.data"
-          in the <literal>javaProperties</literal> option. You can also override
+          in the `javaProperties` option. You can also override
           this in activemq.xml.
         '';
       };
@@ -76,7 +76,7 @@ in {
           "activemq.conf" = "${cfg.configurationDir}";
           "activemq.home" = "${activemq}";
         } // attrs;
-        description = ''
+        description = lib.mdDoc ''
           Specifies Java properties that are sent to the ActiveMQ
           broker service with the "-D" option. You can set properties
           here to change the behaviour and configuration of the broker.
@@ -88,7 +88,7 @@ in {
         type = types.separatedString " ";
         default = "";
         example = "-Xmx2G -Xms2G -XX:MaxPermSize=512M";
-        description = ''
+        description = lib.mdDoc ''
           Add extra options here that you want to be sent to the
           Java runtime when the broker service is started.
         '';
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index 3255942fe438..11dabf0b51c8 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -20,7 +20,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the RabbitMQ server, an Advanced Message
           Queuing Protocol (AMQP) broker.
         '';
@@ -30,7 +30,7 @@ in
         default = pkgs.rabbitmq-server;
         type = types.package;
         defaultText = literalExpression "pkgs.rabbitmq-server";
-        description = ''
+        description = lib.mdDoc ''
           Which rabbitmq package to use.
         '';
       };
@@ -38,12 +38,12 @@ in
       listenAddress = mkOption {
         default = "127.0.0.1";
         example = "";
-        description = ''
+        description = lib.mdDoc ''
           IP address on which RabbitMQ will listen for AMQP
           connections.  Set to the empty string to listen on all
           interfaces.  Note that RabbitMQ creates a user named
-          <literal>guest</literal> with password
-          <literal>guest</literal> by default, so you should delete
+          `guest` with password
+          `guest` by default, so you should delete
           this user if you intend to allow external access.
 
           Together with 'port' setting it's mostly an alias for
@@ -55,7 +55,7 @@ in
 
       port = mkOption {
         default = 5672;
-        description = ''
+        description = lib.mdDoc ''
           Port on which RabbitMQ will listen for AMQP connections.
         '';
         type = types.port;
@@ -64,7 +64,7 @@ in
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/rabbitmq";
-        description = ''
+        description = lib.mdDoc ''
           Data directory for rabbitmq.
         '';
       };
@@ -72,7 +72,7 @@ in
       cookie = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Erlang cookie is a string of arbitrary length which must
           be the same for several nodes to be allowed to communicate.
           Leave empty to generate automatically.
@@ -88,15 +88,15 @@ in
             "auth_backends.1.authz" = "rabbit_auth_backend_internal";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Configuration options in RabbitMQ's new config file format,
           which is a simple key-value format that can not express nested
-          data structures. This is known as the <literal>rabbitmq.conf</literal> file,
+          data structures. This is known as the `rabbitmq.conf` file,
           although outside NixOS that filename may have Erlang syntax, particularly
           prior to RabbitMQ 3.7.0.
 
           If you do need to express nested data structures, you can use
-          <literal>config</literal> option. Configuration from <literal>config</literal>
+          `config` option. Configuration from `config`
           will be merged into these options by RabbitMQ at runtime to
           form the final configuration.
 
@@ -108,14 +108,14 @@ in
       config = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Verbatim advanced configuration file contents using the Erlang syntax.
-          This is also known as the <literal>advanced.config</literal> file or the old config format.
+          This is also known as the `advanced.config` file or the old config format.
 
-          <literal>configItems</literal> is preferred whenever possible. However, nested
-          data structures can only be expressed properly using the <literal>config</literal> option.
+          `configItems` is preferred whenever possible. However, nested
+          data structures can only be expressed properly using the `config` option.
 
-          The contents of this option will be merged into the <literal>configItems</literal>
+          The contents of this option will be merged into the `configItems`
           by RabbitMQ at runtime to form the final configuration.
 
           See the second table on https://www.rabbitmq.com/configure.html#config-items
@@ -126,21 +126,21 @@ in
       plugins = mkOption {
         default = [ ];
         type = types.listOf types.str;
-        description = "The names of plugins to enable";
+        description = lib.mdDoc "The names of plugins to enable";
       };
 
       pluginDirs = mkOption {
         default = [ ];
         type = types.listOf types.path;
-        description = "The list of directories containing external plugins";
+        description = lib.mdDoc "The list of directories containing external plugins";
       };
 
       managementPlugin = {
-        enable = mkEnableOption "the management plugin";
+        enable = mkEnableOption (lib.mdDoc "the management plugin");
         port = mkOption {
           default = 15672;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             On which port to run the management plugin
           '';
         };
diff --git a/nixos/modules/services/audio/alsa.nix b/nixos/modules/services/audio/alsa.nix
index 0d743ed31da8..155780199fd6 100644
--- a/nixos/modules/services/audio/alsa.nix
+++ b/nixos/modules/services/audio/alsa.nix
@@ -25,7 +25,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable ALSA sound.
         '';
       };
@@ -33,7 +33,7 @@ in
       enableOSSEmulation = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable ALSA OSS emulation (with certain cards sound mixing may not work!).
         '';
       };
@@ -44,7 +44,7 @@ in
         example = ''
           defaults.pcm.!card 3
         '';
-        description = ''
+        description = lib.mdDoc ''
           Set addition configuration for system-wide alsa.
         '';
       };
@@ -54,7 +54,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable volume and capture control with keyboard media keys.
 
             You want to leave this disabled if you run a desktop environment
@@ -62,7 +62,7 @@ in
             You might want to enable this if you run a minimalistic desktop
             environment or work from bare linux ttys/framebuffers.
 
-            Enabling this will turn on <option>services.actkbd</option>.
+            Enabling this will turn on {option}`services.actkbd`.
           '';
         };
 
@@ -70,7 +70,7 @@ in
           type = types.str;
           default = "1";
           example = "1%";
-          description = ''
+          description = lib.mdDoc ''
             The value by which to increment/decrement volume on media keys.
 
             See amixer(1) for allowed values.
diff --git a/nixos/modules/services/audio/botamusique.nix b/nixos/modules/services/audio/botamusique.nix
index f4fa0ead4f05..5d3f7db12bc9 100644
--- a/nixos/modules/services/audio/botamusique.nix
+++ b/nixos/modules/services/audio/botamusique.nix
@@ -12,13 +12,13 @@ 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;
       default = pkgs.botamusique;
       defaultText = literalExpression "pkgs.botamusique";
-      description = "The botamusique package to use.";
+      description = lib.mdDoc "The botamusique package to use.";
     };
 
     settings = mkOption {
@@ -29,32 +29,32 @@ in
             type = types.str;
             default = "localhost";
             example = "mumble.example.com";
-            description = "Hostname of the mumble server to connect to.";
+            description = lib.mdDoc "Hostname of the mumble server to connect to.";
           };
 
           server.port = mkOption {
             type = types.port;
             default = 64738;
-            description = "Port of the mumble server to connect to.";
+            description = lib.mdDoc "Port of the mumble server to connect to.";
           };
 
           bot.username = mkOption {
             type = types.str;
             default = "botamusique";
-            description = "Name the bot should appear with.";
+            description = lib.mdDoc "Name the bot should appear with.";
           };
 
           bot.comment = mkOption {
             type = types.str;
             default = "Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun!";
-            description = "Comment displayed for the bot.";
+            description = lib.mdDoc "Comment displayed for the bot.";
           };
         };
       };
       default = {};
-      description = ''
-        Your <filename>configuration.ini</filename> as a Nix attribute set. Look up
-        possible options in the <link xlink:href="https://github.com/azlux/botamusique/blob/master/configuration.example.ini">configuration.example.ini</link>.
+      description = lib.mdDoc ''
+        Your {file}`configuration.ini` as a Nix attribute set. Look up
+        possible options in the [configuration.example.ini](https://github.com/azlux/botamusique/blob/master/configuration.example.ini).
       '';
     };
   };
@@ -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/nixos/modules/services/audio/hqplayerd.nix b/nixos/modules/services/audio/hqplayerd.nix
index 416d12ce2172..eff1549380c8 100644
--- a/nixos/modules/services/audio/hqplayerd.nix
+++ b/nixos/modules/services/audio/hqplayerd.nix
@@ -12,13 +12,13 @@ in
 {
   options = {
     services.hqplayerd = {
-      enable = mkEnableOption "HQPlayer Embedded";
+      enable = mkEnableOption (lib.mdDoc "HQPlayer Embedded");
 
       auth = {
         username = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Username used for HQPlayer's WebUI.
 
             Without this you will need to manually create the credentials after
@@ -29,7 +29,7 @@ in
         password = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Password used for HQPlayer's WebUI.
 
             Without this you will need to manually create the credentials after
@@ -41,7 +41,7 @@ in
       licenseFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to the HQPlayer license key file.
 
           Without this, the service will run in trial mode and restart every 30
@@ -52,7 +52,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Opens ports needed for the WebUI and controller API.
         '';
       };
@@ -60,7 +60,7 @@ in
       config = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           HQplayer daemon configuration, written to /etc/hqplayer/hqplayerd.xml.
 
           Refer to share/doc/hqplayerd/readme.txt in the hqplayerd derivation for possible values.
@@ -133,7 +133,7 @@ in
     users.users = {
       hqplayer = {
         description = "hqplayer daemon user";
-        extraGroups = [ "audio" ];
+        extraGroups = [ "audio" "video" ];
         group = "hqplayer";
         uid = config.ids.uids.hqplayer;
       };
diff --git a/nixos/modules/services/audio/icecast.nix b/nixos/modules/services/audio/icecast.nix
index 5ee5bd745f96..63049bd93ab9 100644
--- a/nixos/modules/services/audio/icecast.nix
+++ b/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 = "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";
       };
@@ -56,51 +56,51 @@ in {
       admin = {
         user = mkOption {
           type = types.str;
-          description = "Username used for all administration functions.";
+          description = lib.mdDoc "Username used for all administration functions.";
           default = "admin";
         };
 
         password = mkOption {
           type = types.str;
-          description = "Password used for all administration functions.";
+          description = lib.mdDoc "Password used for all administration functions.";
         };
       };
 
       logDir = mkOption {
         type = types.path;
-        description = "Base directory used for logging.";
+        description = lib.mdDoc "Base directory used for logging.";
         default = "/var/log/icecast";
       };
 
       listen = {
         port = mkOption {
-          type = types.int;
-          description = "TCP port that will be used to accept client connections.";
+          type = types.port;
+          description = lib.mdDoc "TCP port that will be used to accept client connections.";
           default = 8000;
         };
 
         address = mkOption {
           type = types.str;
-          description = "Address Icecast will listen on.";
+          description = lib.mdDoc "Address Icecast will listen on.";
           default = "::";
         };
       };
 
       user = mkOption {
         type = types.str;
-        description = "User privileges for the server.";
+        description = lib.mdDoc "User privileges for the server.";
         default = "nobody";
       };
 
       group = mkOption {
         type = types.str;
-        description = "Group privileges for the server.";
+        description = lib.mdDoc "Group privileges for the server.";
         default = "nogroup";
       };
 
       extraConf = mkOption {
         type = types.lines;
-        description = "icecast.xml content.";
+        description = lib.mdDoc "icecast.xml content.";
         default = "";
       };
 
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index 84fc9957b879..105e99cb2f5e 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/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.
           '';
         };
@@ -40,14 +40,14 @@ in {
           example = literalExpression ''
             [ "-dalsa" "--device" "hw:1" ];
           '';
-          description = ''
+          description = lib.mdDoc ''
             Specifies startup command line arguments to pass to JACK server.
           '';
         };
 
         session = mkOption {
           type = types.lines;
-          description = ''
+          description = lib.mdDoc ''
             Commands to run after JACK is started.
           '';
         };
@@ -58,7 +58,7 @@ in {
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Route audio to/from generic ALSA-using applications using ALSA JACK PCM plugin.
           '';
         };
@@ -66,7 +66,7 @@ in {
         support32Bit = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to support sound for 32-bit ALSA applications on 64-bit system.
           '';
         };
@@ -76,7 +76,7 @@ in {
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Create ALSA loopback device, instead of using PCM plugin. Has broader
             application support (things like Steam will work), but may need fine-tuning
             for concrete hardware.
@@ -86,14 +86,14 @@ in {
         index = mkOption {
           type = types.int;
           default = 10;
-          description = ''
+          description = lib.mdDoc ''
             Index of an ALSA loopback device.
           '';
         };
 
         config = mkOption {
           type = types.lines;
-          description = ''
+          description = lib.mdDoc ''
             ALSA config for loopback device.
           '';
         };
@@ -105,7 +105,7 @@ in {
             period_size 2048
             periods 2
           '';
-          description = ''
+          description = lib.mdDoc ''
             For music production software that still doesn't support JACK natively you
             would like to put buffer/period adjustments here
             to decrease dmix device latency.
@@ -114,7 +114,7 @@ in {
 
         session = mkOption {
           type = types.lines;
-          description = ''
+          description = lib.mdDoc ''
             Additional commands to run to setup loopback device.
           '';
         };
diff --git a/nixos/modules/services/audio/jmusicbot.nix b/nixos/modules/services/audio/jmusicbot.nix
index e0f8d461af07..c6392c679c04 100644
--- a/nixos/modules/services/audio/jmusicbot.nix
+++ b/nixos/modules/services/audio/jmusicbot.nix
@@ -7,18 +7,18 @@ 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;
         default = pkgs.jmusicbot;
         defaultText = literalExpression "pkgs.jmusicbot";
-        description = "JMusicBot package to use";
+        description = lib.mdDoc "JMusicBot package to use";
       };
 
       stateDir = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The directory where config.txt and serversettings.json is saved.
           If left as the default value this directory will automatically be created before JMusicBot starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions.
           Untouched by the value of this option config.txt needs to be placed manually into this directory.
diff --git a/nixos/modules/services/audio/liquidsoap.nix b/nixos/modules/services/audio/liquidsoap.nix
index ffeefc0f988e..5c10d13af5fd 100644
--- a/nixos/modules/services/audio/liquidsoap.nix
+++ b/nixos/modules/services/audio/liquidsoap.nix
@@ -31,18 +31,20 @@ in
     services.liquidsoap.streams = mkOption {
 
       description =
-        ''
+        lib.mdDoc ''
           Set of Liquidsoap streams to start,
           one systemd service per stream.
         '';
 
       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/nixos/modules/services/audio/mopidy.nix b/nixos/modules/services/audio/mopidy.nix
index 9937feadaeb6..40e8679f53d7 100644
--- a/nixos/modules/services/audio/mopidy.nix
+++ b/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,12 +26,12 @@ 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";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The directory where Mopidy stores its state.
         '';
       };
@@ -40,7 +40,7 @@ in {
         default = [];
         type = types.listOf types.package;
         example = literalExpression "[ pkgs.mopidy-spotify ]";
-        description = ''
+        description = lib.mdDoc ''
           Mopidy extensions that should be loaded by the service.
         '';
       };
@@ -48,7 +48,7 @@ in {
       configuration = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           The configuration that Mopidy should use.
         '';
       };
@@ -56,7 +56,7 @@ in {
       extraConfigFiles = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Extra config file read by Mopidy when the service starts.
           Later files in the list overrides earlier configuration.
         '';
diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix
index 586b9ffa6888..ba1e4716c9b9 100644
--- a/nixos/modules/services/audio/mpd.nix
+++ b/nixos/modules/services/audio/mpd.nix
@@ -56,7 +56,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable MPD, the music player daemon.
         '';
       };
@@ -64,8 +64,8 @@ in {
       startWhenNeeded = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          If set, <command>mpd</command> is socket-activated; that
+        description = lib.mdDoc ''
+          If set, {command}`mpd` is socket-activated; that
           is, instead of having it permanently running as a daemon,
           systemd will start it on the first incoming connection.
         '';
@@ -75,7 +75,7 @@ in {
         type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
         default = "${cfg.dataDir}/music";
         defaultText = literalExpression ''"''${dataDir}/music"'';
-        description = ''
+        description = lib.mdDoc ''
           The directory or NFS/SMB network share where MPD reads music from. If left
           as the default value this directory will automatically be created before
           the MPD server starts, otherwise the sysadmin is responsible for ensuring
@@ -87,7 +87,7 @@ in {
         type = types.path;
         default = "${cfg.dataDir}/playlists";
         defaultText = literalExpression ''"''${dataDir}/playlists"'';
-        description = ''
+        description = lib.mdDoc ''
           The directory where MPD stores playlists. If left as the default value
           this directory will automatically be created before the MPD server starts,
           otherwise the sysadmin is responsible for ensuring the directory exists
@@ -98,18 +98,18 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           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 <literal>man 5 mpd.conf</literal>'.
+          options see `man 5 mpd.conf`'.
         '';
       };
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/${name}";
-        description = ''
+        description = lib.mdDoc ''
           The directory where MPD stores its state, tag cache, playlists etc. If
           left as the default value this directory will automatically be created
           before the MPD server starts, otherwise the sysadmin is responsible for
@@ -120,13 +120,13 @@ in {
       user = mkOption {
         type = types.str;
         default = name;
-        description = "User account under which MPD runs.";
+        description = lib.mdDoc "User account under which MPD runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = name;
-        description = "Group account under which MPD runs.";
+        description = lib.mdDoc "Group account under which MPD runs.";
       };
 
       network = {
@@ -135,16 +135,16 @@ in {
           type = types.str;
           default = "127.0.0.1";
           example = "any";
-          description = ''
+          description = lib.mdDoc ''
             The address for the daemon to listen on.
-            Use <literal>any</literal> to listen on all addresses.
+            Use `any` to listen on all addresses.
           '';
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 6600;
-          description = ''
+          description = lib.mdDoc ''
             This setting is the TCP port that is desired for the daemon to get assigned
             to.
           '';
@@ -156,8 +156,8 @@ in {
         type = types.nullOr types.str;
         default = "${cfg.dataDir}/tag_cache";
         defaultText = literalExpression ''"''${dataDir}/tag_cache"'';
-        description = ''
-          The path to MPD's database. If set to <literal>null</literal> the
+        description = lib.mdDoc ''
+          The path to MPD's database. If set to `null` the
           parameter is omitted from the configuration.
         '';
       };
@@ -167,7 +167,7 @@ in {
           options = {
             passwordFile = mkOption {
               type = types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path to file containing the password.
               '';
             };
@@ -176,14 +176,14 @@ in {
             in mkOption {
               type = types.listOf (types.enum perms);
               default = [ "read" ];
-              description = ''
+              description = lib.mdDoc ''
                 List of permissions that are granted with this password.
                 Permissions can be "${concatStringsSep "\", \"" perms}".
               '';
             };
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           Credentials and permissions for accessing the mpd server.
         '';
         default = [];
@@ -196,7 +196,7 @@ in {
       fluidsynth = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If set, add fluidsynth soundfont and configure the plugin.
         '';
       };
@@ -215,6 +215,7 @@ in {
     systemd.sockets.mpd = mkIf cfg.startWhenNeeded {
       wantedBy = [ "sockets.target" ];
       listenStreams = [
+        ""  # Note: this is needed to override the upstream unit
         (if pkgs.lib.hasPrefix "/" cfg.network.listenAddress
           then cfg.network.listenAddress
           else "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}")
diff --git a/nixos/modules/services/audio/mpdscribble.nix b/nixos/modules/services/audio/mpdscribble.nix
index 333ffb709410..132d9ad32588 100644
--- a/nixos/modules/services/audio/mpdscribble.nix
+++ b/nixos/modules/services/audio/mpdscribble.nix
@@ -77,12 +77,12 @@ in {
 
   options.services.mpdscribble = {
 
-    enable = mkEnableOption "mpdscribble";
+    enable = mkEnableOption (lib.mdDoc "mpdscribble");
 
     proxy = mkOption {
       default = null;
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         HTTP proxy URL.
       '';
     };
@@ -90,7 +90,7 @@ in {
     verbose = mkOption {
       default = 1;
       type = types.int;
-      description = ''
+      description = lib.mdDoc ''
         Log level for the mpdscribble daemon.
       '';
     };
@@ -99,7 +99,7 @@ in {
       default = 600;
       example = 60;
       type = types.int;
-      description = ''
+      description = lib.mdDoc ''
         How often should mpdscribble save the journal file? [seconds]
       '';
     };
@@ -115,7 +115,7 @@ in {
         else "localhost"
       '';
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Host for the mpdscribble daemon to search for a mpd daemon on.
       '';
     };
@@ -128,14 +128,14 @@ 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 = ''
+      description = lib.mdDoc ''
         File containing the password for the mpd daemon.
-        If there is a local mpd configured using <option>services.mpd.credentials</option>
+        If there is a local mpd configured using {option}`services.mpd.credentials`
         the default is automatically set to a matching passwordFile of the local mpd.
       '';
     };
@@ -144,7 +144,7 @@ in {
       default = mpdCfg.network.port;
       defaultText = literalExpression "config.${mpdOpt.network.port}";
       type = types.port;
-      description = ''
+      description = lib.mdDoc ''
         Port for the mpdscribble daemon to search for a mpd daemon on.
       '';
     };
@@ -157,18 +157,18 @@ in {
               type = types.str;
               default = endpointUrls.${name} or "";
               description =
-                "The url endpoint where the scrobble API is listening.";
+                lib.mdDoc "The url endpoint where the scrobble API is listening.";
             };
             username = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Username for the scrobble service.
               '';
             };
             passwordFile = mkOption {
               type = types.nullOr types.str;
               description =
-                "File containing the password, either as MD5SUM or cleartext.";
+                lib.mdDoc "File containing the password, either as MD5SUM or cleartext.";
             };
           };
         };
@@ -180,7 +180,7 @@ in {
           passwordFile = "/run/secrets/lastfm_password";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         Endpoints to scrobble to.
         If the endpoint is one of "${
           concatStringsSep "\", \"" (attrNames endpointUrls)
diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix
index 3660e05310be..e73828081d4b 100644
--- a/nixos/modules/services/audio/navidrome.nix
+++ b/nixos/modules/services/audio/navidrome.nix
@@ -9,7 +9,7 @@ in {
   options = {
     services.navidrome = {
 
-      enable = mkEnableOption "Navidrome music server";
+      enable = mkEnableOption (lib.mdDoc "Navidrome music server");
 
       settings = mkOption rec {
         type = settingsFormat.type;
@@ -21,8 +21,8 @@ in {
         example = {
           MusicFolder = "/mnt/music";
         };
-        description = ''
-          Configuration for Navidrome, see <link xlink:href="https://www.navidrome.org/docs/usage/configuration-options/"/> for supported values.
+        description = lib.mdDoc ''
+          Configuration for Navidrome, see <https://www.navidrome.org/docs/usage/configuration-options/> for supported values.
         '';
       };
 
@@ -45,7 +45,10 @@ in {
         RootDirectory = "/run/navidrome";
         ReadWritePaths = "";
         BindReadOnlyPaths = [
+          # navidrome uses online services to download additional album metadata / covers
+          "${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt"
           builtins.storeDir
+          "/etc"
         ] ++ lib.optional (cfg.settings ? MusicFolder) cfg.settings.MusicFolder;
         CapabilityBoundingSet = "";
         RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
@@ -59,7 +62,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/nixos/modules/services/audio/networkaudiod.nix b/nixos/modules/services/audio/networkaudiod.nix
index 265a4e1d95d6..11486429e667 100644
--- a/nixos/modules/services/audio/networkaudiod.nix
+++ b/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/nixos/modules/services/audio/roon-bridge.nix b/nixos/modules/services/audio/roon-bridge.nix
index e08f8a4f9e7a..db84ba286221 100644
--- a/nixos/modules/services/audio/roon-bridge.nix
+++ b/nixos/modules/services/audio/roon-bridge.nix
@@ -8,25 +8,25 @@ let
 in {
   options = {
     services.roon-bridge = {
-      enable = mkEnableOption "Roon Bridge";
+      enable = mkEnableOption (lib.mdDoc "Roon Bridge");
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the bridge.
         '';
       };
       user = mkOption {
         type = types.str;
         default = "roon-bridge";
-        description = ''
+        description = lib.mdDoc ''
           User to run the Roon bridge as.
         '';
       };
       group = mkOption {
         type = types.str;
         default = "roon-bridge";
-        description = ''
+        description = lib.mdDoc ''
           Group to run the Roon Bridge as.
         '';
       };
diff --git a/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
index de1f61c8e73b..74cae909f5db 100644
--- a/nixos/modules/services/audio/roon-server.nix
+++ b/nixos/modules/services/audio/roon-server.nix
@@ -8,25 +8,25 @@ let
 in {
   options = {
     services.roon-server = {
-      enable = mkEnableOption "Roon Server";
+      enable = mkEnableOption (lib.mdDoc "Roon Server");
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the server.
         '';
       };
       user = mkOption {
         type = types.str;
         default = "roon-server";
-        description = ''
+        description = lib.mdDoc ''
           User to run the Roon Server as.
         '';
       };
       group = mkOption {
         type = types.str;
         default = "roon-server";
-        description = ''
+        description = lib.mdDoc ''
           Group to run the Roon Server as.
         '';
       };
@@ -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";
@@ -53,10 +54,12 @@ in {
     networking.firewall = mkIf cfg.openFirewall {
       allowedTCPPortRanges = [
         { from = 9100; to = 9200; }
-        { from = 9330; to = 9332; }
+        { from = 9330; to = 9339; }
+        { from = 30000; to = 30010; }
       ];
       allowedUDPPorts = [ 9003 ];
       extraCommands = ''
+        ## IGMP / Broadcast ##
         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
diff --git a/nixos/modules/services/audio/slimserver.nix b/nixos/modules/services/audio/slimserver.nix
index ecd265284990..9fbc68b71364 100644
--- a/nixos/modules/services/audio/slimserver.nix
+++ b/nixos/modules/services/audio/slimserver.nix
@@ -14,7 +14,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable slimserver.
         '';
       };
@@ -23,13 +23,13 @@ in {
         type = types.package;
         default = pkgs.slimserver;
         defaultText = literalExpression "pkgs.slimserver";
-        description = "Slimserver package to use.";
+        description = lib.mdDoc "Slimserver package to use.";
       };
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/slimserver";
-        description = ''
+        description = lib.mdDoc ''
           The directory where slimserver stores its state, tag cache,
           playlists etc.
         '';
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index 91d97a0b551e..2af42eeb3705 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -12,7 +12,7 @@ let
   sampleFormat = mkOption {
     type = with types; nullOr str;
     default = null;
-    description = ''
+    description = lib.mdDoc ''
       Default sample format.
     '';
     example = "48000:16:2";
@@ -21,7 +21,7 @@ let
   codec = mkOption {
     type = with types; nullOr str;
     default = null;
-    description = ''
+    description = lib.mdDoc ''
       Default audio compression method.
     '';
     example = "flac";
@@ -77,7 +77,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable snapserver.
         '';
       };
@@ -86,7 +86,7 @@ in {
         type = types.str;
         default = "::";
         example = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           The address where snapclients can connect.
         '';
       };
@@ -94,17 +94,15 @@ in {
       port = mkOption {
         type = types.port;
         default = 1704;
-        description = ''
+        description = lib.mdDoc ''
           The port that snapclients can connect to.
         '';
       };
 
       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;
-        description = ''
+        default = false;
+        description = lib.mdDoc ''
           Whether to automatically open the specified ports in the firewall.
         '';
       };
@@ -115,7 +113,7 @@ in {
       streamBuffer = mkOption {
         type = with types; nullOr int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Stream read (input) buffer in ms.
         '';
         example = 20;
@@ -124,7 +122,7 @@ in {
       buffer = mkOption {
         type = with types; nullOr int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Network buffer in ms.
         '';
         example = 1000;
@@ -133,7 +131,7 @@ in {
       sendToMuted = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Send audio to muted clients.
         '';
       };
@@ -141,7 +139,7 @@ in {
       tcp.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the JSON-RPC via TCP.
         '';
       };
@@ -150,7 +148,7 @@ in {
         type = types.str;
         default = "::";
         example = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           The address where the TCP JSON-RPC listens on.
         '';
       };
@@ -158,7 +156,7 @@ in {
       tcp.port = mkOption {
         type = types.port;
         default = 1705;
-        description = ''
+        description = lib.mdDoc ''
           The port where the TCP JSON-RPC listens on.
         '';
       };
@@ -166,7 +164,7 @@ in {
       http.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the JSON-RPC via HTTP.
         '';
       };
@@ -175,7 +173,7 @@ in {
         type = types.str;
         default = "::";
         example = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           The address where the HTTP JSON-RPC listens on.
         '';
       };
@@ -183,7 +181,7 @@ in {
       http.port = mkOption {
         type = types.port;
         default = 1780;
-        description = ''
+        description = lib.mdDoc ''
           The port where the HTTP JSON-RPC listens on.
         '';
       };
@@ -191,7 +189,7 @@ in {
       http.docRoot = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to serve from the HTTP servers root.
         '';
       };
@@ -201,12 +199,12 @@ in {
           options = {
             location = mkOption {
               type = types.oneOf [ types.path types.str ];
-              description = ''
-                For type <literal>pipe</literal> or <literal>file</literal>, the path to the pipe or file.
-                For type <literal>librespot</literal>, <literal>airplay</literal> or <literal>process</literal>, the path to the corresponding binary.
-                For type <literal>tcp</literal>, the <literal>host:port</literal> address to connect to or listen on.
-                For type <literal>meta</literal>, a list of stream names in the form <literal>/one/two/...</literal>. Don't forget the leading slash.
-                For type <literal>alsa</literal>, use an empty string.
+              description = lib.mdDoc ''
+                For type `pipe` or `file`, the path to the pipe or file.
+                For type `librespot`, `airplay` or `process`, the path to the corresponding binary.
+                For type `tcp`, the `host:port` address to connect to or listen on.
+                For type `meta`, a list of stream names in the form `/one/two/...`. Don't forget the leading slash.
+                For type `alsa`, use an empty string.
               '';
               example = literalExpression ''
                 "/path/to/pipe"
@@ -218,14 +216,14 @@ in {
             type = mkOption {
               type = types.enum [ "pipe" "librespot" "airplay" "file" "process" "tcp" "alsa" "spotify" "meta" ];
               default = "pipe";
-              description = ''
+              description = lib.mdDoc ''
                 The type of input stream.
               '';
             };
             query = mkOption {
               type = attrsOf str;
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 Key-value pairs that convey additional parameters about a stream.
               '';
               example = literalExpression ''
@@ -253,7 +251,7 @@ in {
           };
         });
         default = { default = {}; };
-        description = ''
+        description = lib.mdDoc ''
           The definition for an input source.
         '';
         example = literalExpression ''
@@ -279,12 +277,7 @@ in {
       # https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
       filter (w: w != "") (mapAttrsToList (k: v: if v.type == "spotify" then ''
         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.
-      '';
+      '' else "") cfg.streams);
 
     systemd.services.snapserver = {
       after = [ "network.target" ];
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
index 22848ed98000..975be5a87cba 100644
--- a/nixos/modules/services/audio/spotifyd.nix
+++ b/nixos/modules/services/audio/spotifyd.nix
@@ -17,14 +17,14 @@ in
 {
   options = {
     services.spotifyd = {
-      enable = mkEnableOption "spotifyd, a Spotify playing daemon";
+      enable = mkEnableOption (lib.mdDoc "spotifyd, a Spotify playing daemon");
 
       config = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           (Deprecated) Configuration for Spotifyd. For syntax and directives, see
-          <link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
+          <https://github.com/Spotifyd/spotifyd#Configuration>.
         '';
       };
 
@@ -32,9 +32,9 @@ in
         default = {};
         type = toml.type;
         example = { global.bitrate = 320; };
-        description = ''
+        description = lib.mdDoc ''
           Configuration for Spotifyd. For syntax and directives, see
-          <link xlink:href="https://github.com/Spotifyd/spotifyd#Configuration"/>.
+          <https://github.com/Spotifyd/spotifyd#Configuration>.
         '';
       };
     };
diff --git a/nixos/modules/services/audio/squeezelite.nix b/nixos/modules/services/audio/squeezelite.nix
index 36295e21c60f..30dc12552f00 100644
--- a/nixos/modules/services/audio/squeezelite.nix
+++ b/nixos/modules/services/audio/squeezelite.nix
@@ -14,14 +14,14 @@ 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 = "";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Additional command line arguments to pass to Squeezelite.
       '';
     };
diff --git a/nixos/modules/services/audio/ympd.nix b/nixos/modules/services/audio/ympd.nix
index 84b72d142513..811b81030efc 100644
--- a/nixos/modules/services/audio/ympd.nix
+++ b/nixos/modules/services/audio/ympd.nix
@@ -12,12 +12,12 @@ 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
         default = "8080";
-        description = "The port where ympd's web interface will be available.";
+        description = lib.mdDoc "The port where ympd's web interface will be available.";
         example = "ssl://8080:/path/to/ssl-private-key.pem";
       };
 
@@ -25,14 +25,14 @@ in {
         host = mkOption {
           type = types.str;
           default = "localhost";
-          description = "The host where MPD is listening.";
+          description = lib.mdDoc "The host where MPD is listening.";
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = config.services.mpd.network.port;
           defaultText = literalExpression "config.services.mpd.network.port";
-          description = "The port where MPD is listening.";
+          description = lib.mdDoc "The port where MPD is listening.";
           example = 6600;
         };
       };
diff --git a/nixos/modules/services/backup/automysqlbackup.nix b/nixos/modules/services/backup/automysqlbackup.nix
index fd2764a40ad2..d0237f196a80 100644
--- a/nixos/modules/services/backup/automysqlbackup.nix
+++ b/nixos/modules/services/backup/automysqlbackup.nix
@@ -30,12 +30,12 @@ in
   options = {
     services.automysqlbackup = {
 
-      enable = mkEnableOption "AutoMySQLBackup";
+      enable = mkEnableOption (lib.mdDoc "AutoMySQLBackup");
 
       calendar = mkOption {
         type = types.str;
         default = "01:15:00";
-        description = ''
+        description = lib.mdDoc ''
           Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second).
         '';
       };
@@ -43,9 +43,9 @@ in
       config = mkOption {
         type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           automysqlbackup configuration. Refer to
-          <filename>''${pkgs.automysqlbackup}/etc/automysqlbackup.conf</filename>
+          {file}`''${pkgs.automysqlbackup}/etc/automysqlbackup.conf`
           for details on supported values.
         '';
         example = literalExpression ''
@@ -112,7 +112,7 @@ in
 
     services.mysql.ensureUsers = optional (config.services.mysql.enable && cfg.config.mysql_dump_host == "localhost") {
       name = user;
-      ensurePermissions = { "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES"; };
+      ensurePermissions = { "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT"; };
     };
 
   };
diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix
index 598902042346..0acbf1b3eabb 100644
--- a/nixos/modules/services/backup/bacula.nix
+++ b/nixos/modules/services/backup/bacula.nix
@@ -114,7 +114,7 @@ let
       password = mkOption {
         type = types.str;
         # TODO: required?
-        description = ''
+        description = lib.mdDoc ''
           Specifies the password that must be supplied for the default Bacula
           Console to be authorized. The same password must appear in the
           Director resource of the Console configuration file. For added
@@ -135,10 +135,10 @@ let
         type = types.enum [ "no" "yes" ];
         default = "no";
         example = "yes";
-        description = ''
-          If Monitor is set to <literal>no</literal>, this director will have
+        description = lib.mdDoc ''
+          If Monitor is set to `no`, this director will have
           full access to this Storage daemon. If Monitor is set to
-          <literal>yes</literal>, this director will only be able to fetch the
+          `yes`, this director will only be able to fetch the
           current status of this Storage daemon.
 
           Please note that if this director is being used by a Monitor, we
@@ -154,15 +154,15 @@ let
     options = {
       changerDevice = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The specified name-string must be the generic SCSI device name of the
           autochanger that corresponds to the normal read/write Archive Device
           specified in the Device resource. This generic SCSI device name
           should be specified if you have an autochanger or if you have a
           standard tape drive and want to use the Alert Command (see below).
           For example, on Linux systems, for an Archive Device name of
-          <literal>/dev/nst0</literal>, you would specify
-          <literal>/dev/sg0</literal> for the Changer Device name.  Depending
+          `/dev/nst0`, you would specify
+          `/dev/sg0` for the Changer Device name.  Depending
           on your exact configuration, and the number of autochangers or the
           type of autochanger, what you specify here can vary. This directive
           is optional. See the Using AutochangersAutochangersChapter chapter of
@@ -173,7 +173,7 @@ let
 
       changerCommand = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The name-string specifies an external program to be called that will
           automatically change volumes as required by Bacula. Normally, this
           directive will be specified only in the AutoChanger resource, which
@@ -181,7 +181,7 @@ let
           different Changer Command in each Device resource. Most frequently,
           you will specify the Bacula supplied mtx-changer script as follows:
 
-          <literal>"/path/mtx-changer %c %o %S %a %d"</literal>
+          `"/path/mtx-changer %c %o %S %a %d"`
 
           and you will install the mtx on your system (found in the depkgs
           release). An example of this command is in the default bacula-sd.conf
@@ -195,14 +195,14 @@ let
       };
 
       devices = mkOption {
-        description = "";
+        description = lib.mdDoc "";
         type = types.listOf types.str;
       };
 
       extraAutochangerConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Autochanger directive.
         '';
         example = ''
@@ -219,13 +219,13 @@ let
       archiveDevice = mkOption {
         # TODO: required?
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The specified name-string gives the system file name of the storage
           device managed by this storage daemon. This will usually be the
           device file name of a removable storage device (tape drive), for
-          example <literal>/dev/nst0</literal> or
-          <literal>/dev/rmt/0mbn</literal>. For a DVD-writer, it will be for
-          example <literal>/dev/hdc</literal>. It may also be a directory name
+          example `/dev/nst0` or
+          `/dev/rmt/0mbn`. For a DVD-writer, it will be for
+          example `/dev/hdc`. It may also be a directory name
           if you are archiving to disk storage. In this case, you must supply
           the full absolute path to the directory. When specifying a tape
           device, it is preferable that the "non-rewind" variant of the device
@@ -236,9 +236,9 @@ let
       mediaType = mkOption {
         # TODO: required?
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The specified name-string names the type of media supported by this
-          device, for example, <literal>DLT7000</literal>. Media type names are
+          device, for example, `DLT7000`. Media type names are
           arbitrary in that you set them to anything you want, but they must be
           known to the volume database to keep track of which storage daemons
           can read which volumes. In general, each different storage type
@@ -255,9 +255,9 @@ let
           Storage daemon, but it is with multiple Storage daemons, especially
           if they have incompatible media.
 
-          For example, if you specify a Media Type of <literal>DDS-4</literal>
+          For example, if you specify a Media Type of `DDS-4`
           then during the restore, Bacula will be able to choose any Storage
-          Daemon that handles <literal>DDS-4</literal>. If you have an
+          Daemon that handles `DDS-4`. If you have an
           autochanger, you might want to name the Media Type in a way that is
           unique to the autochanger, unless you wish to possibly use the
           Volumes in other drives. You should also ensure to have unique Media
@@ -274,7 +274,7 @@ let
       extraDeviceConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Device directive.
         '';
         example = ''
@@ -295,7 +295,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Bacula File Daemon.
         '';
       };
@@ -304,7 +304,7 @@ in {
         default = "${config.networking.hostName}-fd";
         defaultText = literalExpression ''"''${config.networking.hostName}-fd"'';
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The client name that must be used by the Director when connecting.
           Generally, it is a good idea to use a name related to the machine so
           that error messages can be easily identified if you have multiple
@@ -314,8 +314,8 @@ in {
 
       port = mkOption {
         default = 9102;
-        type = types.int;
-        description = ''
+        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
           the Client resource of the Director's configuration file.
@@ -324,7 +324,7 @@ in {
 
       director = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines director resources in Bacula File Daemon.
         '';
         type = with types; attrsOf (submodule directorOptions);
@@ -333,7 +333,7 @@ in {
       extraClientConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Client directive.
         '';
         example = ''
@@ -345,7 +345,7 @@ in {
       extraMessagesConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Messages directive.
         '';
         example = ''
@@ -358,7 +358,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Bacula Storage Daemon.
         '';
       };
@@ -367,15 +367,15 @@ in {
         default = "${config.networking.hostName}-sd";
         defaultText = literalExpression ''"''${config.networking.hostName}-sd"'';
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the Name of the Storage daemon.
         '';
       };
 
       port = mkOption {
         default = 9103;
-        type = types.int;
-        description = ''
+        type = types.port;
+        description = lib.mdDoc ''
           Specifies port number on which the Storage daemon listens for
           Director connections.
         '';
@@ -383,7 +383,7 @@ in {
 
       director = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines Director resources in Bacula Storage Daemon.
         '';
         type = with types; attrsOf (submodule directorOptions);
@@ -391,7 +391,7 @@ in {
 
       device = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines Device resources in Bacula Storage Daemon.
         '';
         type = with types; attrsOf (submodule deviceOptions);
@@ -399,7 +399,7 @@ in {
 
       autochanger = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines Autochanger resources in Bacula Storage Daemon.
         '';
         type = with types; attrsOf (submodule autochangerOptions);
@@ -408,7 +408,7 @@ in {
       extraStorageConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Storage directive.
         '';
         example = ''
@@ -420,7 +420,7 @@ in {
       extraMessagesConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Messages directive.
         '';
         example = ''
@@ -434,7 +434,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Bacula Director Daemon.
         '';
       };
@@ -443,7 +443,7 @@ in {
         default = "${config.networking.hostName}-dir";
         defaultText = literalExpression ''"''${config.networking.hostName}-dir"'';
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The director name used by the system administrator. This directive is
           required.
         '';
@@ -451,8 +451,8 @@ in {
 
       port = mkOption {
         default = 9101;
-        type = types.int;
-        description = ''
+        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
           must be specified in the Director resource of the Console
@@ -465,7 +465,7 @@ in {
       password = mkOption {
         # TODO: required?
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
            Specifies the password that must be supplied for a Director.
         '';
       };
@@ -473,7 +473,7 @@ in {
       extraMessagesConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Messages directive.
         '';
         example = ''
@@ -484,7 +484,7 @@ in {
       extraDirectorConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be passed in Director directive.
         '';
         example = ''
@@ -496,7 +496,7 @@ in {
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration for Bacula Director Daemon.
         '';
         example = ''
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 4c9ddfe4674b..ae8e1dd8463b 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/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,16 +23,15 @@ 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=$?
-      # Reset the EXIT handler, or else we're called again on 'exit' below
-      trap - EXIT
       ${cfg.postHook}
       exit $exitStatus
     }
-    trap 'on_exit' INT TERM QUIT EXIT
+    trap on_exit EXIT
 
     archiveName="${if cfg.archiveBaseName == null then "" else cfg.archiveBaseName + "-"}$(date ${cfg.dateFormat})"
     archiveSuffix="${optionalString cfg.appendFailedSuffix ".failed"}"
@@ -48,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}
@@ -60,10 +64,10 @@ 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
     ${cfg.postPrune}
-  '';
+  '');
 
   mkPassEnv = cfg: with cfg.encryption;
     if passCommand != null then
@@ -75,12 +79,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;
@@ -118,7 +129,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)}
@@ -219,7 +230,7 @@ in {
   ###### interface
 
   options.services.borgbackup.jobs = mkOption {
-    description = ''
+    description = lib.mdDoc ''
       Deduplicating backups using BorgBackup.
       Adding a job will cause a borg-job-NAME wrapper to be added
       to your system path, so that you can perform maintenance easily.
@@ -265,9 +276,9 @@ in {
           paths = mkOption {
             type = with types; nullOr (coercedTo str lib.singleton (listOf str));
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Path(s) to back up.
-              Mutually exclusive with <option>dumpCommand</option>.
+              Mutually exclusive with {option}`dumpCommand`.
             '';
             example = "/home/user";
           };
@@ -275,42 +286,42 @@ in {
           dumpCommand = mkOption {
             type = with types; nullOr path;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Backup the stdout of this program instead of filesystem paths.
-              Mutually exclusive with <option>paths</option>.
+              Mutually exclusive with {option}`paths`.
             '';
             example = "/path/to/createZFSsend.sh";
           };
 
           repo = mkOption {
             type = types.str;
-            description = "Remote or local repository to back up to.";
+            description = lib.mdDoc "Remote or local repository to back up to.";
             example = "user@machine:/path/to/repo";
           };
 
           removableDevice = mkOption {
             type = types.bool;
             default = false;
-            description = "Whether the repo (which must be local) is a removable device.";
+            description = lib.mdDoc "Whether the repo (which must be local) is a removable device.";
           };
 
           archiveBaseName = mkOption {
             type = types.nullOr (types.strMatching "[^/{}]+");
             default = "${globalConfig.networking.hostName}-${name}";
             defaultText = literalExpression ''"''${config.networking.hostName}-<name>"'';
-            description = ''
+            description = lib.mdDoc ''
               How to name the created archives. A timestamp, whose format is
-              determined by <option>dateFormat</option>, will be appended. The full
-              name can be modified at runtime (<literal>$archiveName</literal>).
-              Placeholders like <literal>{hostname}</literal> must not be used.
-              Use <literal>null</literal> for no base name.
+              determined by {option}`dateFormat`, will be appended. The full
+              name can be modified at runtime (`$archiveName`).
+              Placeholders like `{hostname}` must not be used.
+              Use `null` for no base name.
             '';
           };
 
           dateFormat = mkOption {
             type = types.str;
-            description = ''
-              Arguments passed to <command>date</command>
+            description = lib.mdDoc ''
+              Arguments passed to {command}`date`
               to create a timestamp suffix for the archive name.
             '';
             default = "+%Y-%m-%dT%H:%M:%S";
@@ -320,13 +331,12 @@ in {
           startAt = mkOption {
             type = with types; either str (listOf str);
             default = "daily";
-            description = ''
+            description = lib.mdDoc ''
               When or how often the backup should run.
               Must be in the format described in
-              <citerefentry><refentrytitle>systemd.time</refentrytitle>
-              <manvolnum>7</manvolnum></citerefentry>.
+              {manpage}`systemd.time(7)`.
               If you do not want the backup to start
-              automatically, use <literal>[ ]</literal>.
+              automatically, use `[ ]`.
               It will generate a systemd service borgbackup-job-NAME.
               You may trigger it manually via systemctl restart borgbackup-job-NAME.
             '';
@@ -336,30 +346,38 @@ in {
             default = false;
             type = types.bool;
             example = true;
-            description = ''
-              Set the <literal>persistentTimer</literal> option for the
-              <citerefentry><refentrytitle>systemd.timer</refentrytitle>
-              <manvolnum>5</manvolnum></citerefentry>
+            description = lib.mdDoc ''
+              Set the `persistentTimer` option for the
+              {manpage}`systemd.timer(5)`
               which triggers the backup immediately if the last trigger
               was missed (e.g. if the system was powered down).
             '';
           };
 
+          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 = ''
-              The user <command>borg</command> is run as.
+            description = lib.mdDoc ''
+              The user {command}`borg` is run as.
               User or group need read permission
-              for the specified <option>paths</option>.
+              for the specified {option}`paths`.
             '';
             default = "root";
           };
 
           group = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               The group borg is run as. User or group needs read permission
-              for the specified <option>paths</option>.
+              for the specified {option}`paths`.
             '';
             default = "root";
           };
@@ -371,20 +389,20 @@ in {
               "authenticated" "authenticated-blake2"
               "none"
             ];
-            description = ''
+            description = lib.mdDoc ''
               Encryption mode to use. Setting a mode
-              other than <literal>"none"</literal> requires
-              you to specify a <option>passCommand</option>
-              or a <option>passphrase</option>.
+              other than `"none"` requires
+              you to specify a {option}`passCommand`
+              or a {option}`passphrase`.
             '';
             example = "repokey-blake2";
           };
 
           encryption.passCommand = mkOption {
             type = with types; nullOr str;
-            description = ''
+            description = lib.mdDoc ''
               A command which prints the passphrase to stdout.
-              Mutually exclusive with <option>passphrase</option>.
+              Mutually exclusive with {option}`passphrase`.
             '';
             default = null;
             example = "cat /path/to/passphrase_file";
@@ -392,11 +410,11 @@ in {
 
           encryption.passphrase = mkOption {
             type = with types; nullOr str;
-            description = ''
+            description = lib.mdDoc ''
               The passphrase the backups are encrypted with.
-              Mutually exclusive with <option>passCommand</option>.
+              Mutually exclusive with {option}`passCommand`.
               If you do not want the passphrase to be stored in the
-              world-readable Nix store, use <option>passCommand</option>.
+              world-readable Nix store, use {option}`passCommand`.
             '';
             default = null;
           };
@@ -406,9 +424,9 @@ in {
             # compression mode must be given,
             # compression level is optional
             type = types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
-            description = ''
+            description = lib.mdDoc ''
               Compression method to use. Refer to
-              <command>borg help compression</command>
+              {command}`borg help compression`
               for all available options.
             '';
             default = "lz4";
@@ -417,9 +435,9 @@ in {
 
           exclude = mkOption {
             type = with types; listOf str;
-            description = ''
+            description = lib.mdDoc ''
               Exclude paths matching any of the given patterns. See
-              <command>borg help patterns</command> for pattern syntax.
+              {command}`borg help patterns` for pattern syntax.
             '';
             default = [ ];
             example = [
@@ -428,11 +446,26 @@ 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 = ''
+            description = lib.mdDoc ''
               By default, borg cannot write anywhere on the system but
-              <literal>$HOME/.config/borg</literal> and <literal>$HOME/.cache/borg</literal>.
+              `$HOME/.config/borg` and `$HOME/.cache/borg`.
               If, for example, your preHook script needs to dump files
               somewhere, put those directories here.
             '';
@@ -444,8 +477,8 @@ in {
 
           privateTmp = mkOption {
             type = types.bool;
-            description = ''
-              Set the <literal>PrivateTmp</literal> option for
+            description = lib.mdDoc ''
+              Set the `PrivateTmp` option for
               the systemd-service. Set to false if you need sockets
               or other files from global /tmp.
             '';
@@ -454,10 +487,10 @@ in {
 
           doInit = mkOption {
             type = types.bool;
-            description = ''
-              Run <command>borg init</command> if the
-              specified <option>repo</option> does not exist.
-              You should set this to <literal>false</literal>
+            description = lib.mdDoc ''
+              Run {command}`borg init` if the
+              specified {option}`repo` does not exist.
+              You should set this to `false`
               if the repository is located on an external drive
               that might not always be mounted.
             '';
@@ -466,10 +499,10 @@ in {
 
           appendFailedSuffix = mkOption {
             type = types.bool;
-            description = ''
-              Append a <literal>.failed</literal> suffix
+            description = lib.mdDoc ''
+              Append a `.failed` suffix
               to the archive name, which is only removed if
-              <command>borg create</command> has a zero exit status.
+              {command}`borg create` has a zero exit status.
             '';
             default = true;
           };
@@ -479,9 +512,9 @@ in {
             # means there is no limit of yearly archives to keep
             # The regex is for use with e.g. --keep-within 1y
             type = with types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
-            description = ''
+            description = lib.mdDoc ''
               Prune a repository by deleting all archives not matching any of the
-              specified retention options. See <command>borg help prune</command>
+              specified retention options. See {command}`borg help prune`
               for the available options.
             '';
             default = { };
@@ -497,10 +530,10 @@ in {
 
           prune.prefix = mkOption {
             type = types.nullOr (types.str);
-            description = ''
+            description = lib.mdDoc ''
               Only consider archive names starting with this prefix for pruning.
               By default, only archives created by this job are considered.
-              Use <literal>""</literal> or <literal>null</literal> to consider all archives.
+              Use `""` or `null` to consider all archives.
             '';
             default = config.archiveBaseName;
             defaultText = literalExpression "archiveBaseName";
@@ -508,7 +541,7 @@ in {
 
           environment = mkOption {
             type = with types; attrsOf str;
-            description = ''
+            description = lib.mdDoc ''
               Environment variables passed to the backup script.
               You can for example specify which SSH key to use.
             '';
@@ -518,7 +551,7 @@ in {
 
           preHook = mkOption {
             type = types.lines;
-            description = ''
+            description = lib.mdDoc ''
               Shell commands to run before the backup.
               This can for example be used to mount file systems.
             '';
@@ -531,43 +564,43 @@ in {
 
           postInit = mkOption {
             type = types.lines;
-            description = ''
-              Shell commands to run after <command>borg init</command>.
+            description = lib.mdDoc ''
+              Shell commands to run after {command}`borg init`.
             '';
             default = "";
           };
 
           postCreate = mkOption {
             type = types.lines;
-            description = ''
-              Shell commands to run after <command>borg create</command>. The name
-              of the created archive is stored in <literal>$archiveName</literal>.
+            description = lib.mdDoc ''
+              Shell commands to run after {command}`borg create`. The name
+              of the created archive is stored in `$archiveName`.
             '';
             default = "";
           };
 
           postPrune = mkOption {
             type = types.lines;
-            description = ''
-              Shell commands to run after <command>borg prune</command>.
+            description = lib.mdDoc ''
+              Shell commands to run after {command}`borg prune`.
             '';
             default = "";
           };
 
           postHook = mkOption {
             type = types.lines;
-            description = ''
+            description = lib.mdDoc ''
               Shell commands to run just before exit. They are executed
               even if a previous command exits with a non-zero exit code.
-              The latter is available as <literal>$exitStatus</literal>.
+              The latter is available as `$exitStatus`.
             '';
             default = "";
           };
 
           extraArgs = mkOption {
             type = types.str;
-            description = ''
-              Additional arguments for all <command>borg</command> calls the
+            description = lib.mdDoc ''
+              Additional arguments for all {command}`borg` calls the
               service has. Handle with care.
             '';
             default = "";
@@ -576,9 +609,9 @@ in {
 
           extraInitArgs = mkOption {
             type = types.str;
-            description = ''
-              Additional arguments for <command>borg init</command>.
-              Can also be set at runtime using <literal>$extraInitArgs</literal>.
+            description = lib.mdDoc ''
+              Additional arguments for {command}`borg init`.
+              Can also be set at runtime using `$extraInitArgs`.
             '';
             default = "";
             example = "--append-only";
@@ -586,9 +619,9 @@ in {
 
           extraCreateArgs = mkOption {
             type = types.str;
-            description = ''
-              Additional arguments for <command>borg create</command>.
-              Can also be set at runtime using <literal>$extraCreateArgs</literal>.
+            description = lib.mdDoc ''
+              Additional arguments for {command}`borg create`.
+              Can also be set at runtime using `$extraCreateArgs`.
             '';
             default = "";
             example = "--stats --checkpoint-interval 600";
@@ -596,9 +629,9 @@ in {
 
           extraPruneArgs = mkOption {
             type = types.str;
-            description = ''
-              Additional arguments for <command>borg prune</command>.
-              Can also be set at runtime using <literal>$extraPruneArgs</literal>.
+            description = lib.mdDoc ''
+              Additional arguments for {command}`borg prune`.
+              Can also be set at runtime using `$extraPruneArgs`.
             '';
             default = "";
             example = "--save-space";
@@ -610,12 +643,12 @@ in {
   };
 
   options.services.borgbackup.repos = mkOption {
-    description = ''
+    description = lib.mdDoc ''
       Serve BorgBackup repositories to given public SSH keys,
       restricting their access to the repository only.
       See also the chapter about BorgBackup in the NixOS manual.
       Also, clients do not need to specify the absolute path when accessing the repository,
-      i.e. <literal>user@machine:.</literal> is enough. (Note colon and dot.)
+      i.e. `user@machine:.` is enough. (Note colon and dot.)
     '';
     default = { };
     type = types.attrsOf (types.submodule (
@@ -623,7 +656,7 @@ in {
         options = {
           path = mkOption {
             type = types.path;
-            description = ''
+            description = lib.mdDoc ''
               Where to store the backups. Note that the directory
               is created automatically, with correct permissions.
             '';
@@ -632,30 +665,30 @@ in {
 
           user = mkOption {
             type = types.str;
-            description = ''
-              The user <command>borg serve</command> is run as.
+            description = lib.mdDoc ''
+              The user {command}`borg serve` is run as.
               User or group needs write permission
-              for the specified <option>path</option>.
+              for the specified {option}`path`.
             '';
             default = "borg";
           };
 
           group = mkOption {
             type = types.str;
-            description = ''
-              The group <command>borg serve</command> is run as.
+            description = lib.mdDoc ''
+              The group {command}`borg serve` is run as.
               User or group needs write permission
-              for the specified <option>path</option>.
+              for the specified {option}`path`.
             '';
             default = "borg";
           };
 
           authorizedKeys = mkOption {
             type = with types; listOf str;
-            description = ''
+            description = lib.mdDoc ''
               Public SSH keys that are given full write access to this repository.
               You should use a different SSH key for each repository you write to, because
-              the specified keys are restricted to running <command>borg serve</command>
+              the specified keys are restricted to running {command}`borg serve`
               and can only access this single repository.
             '';
             default = [ ];
@@ -663,7 +696,7 @@ in {
 
           authorizedKeysAppendOnly = mkOption {
             type = with types; listOf str;
-            description = ''
+            description = lib.mdDoc ''
               Public SSH keys that can only be used to append new data (archives) to the repository.
               Note that archives can still be marked as deleted and are subsequently removed from disk
               upon accessing the repo with full write access, e.g. when pruning.
@@ -673,11 +706,11 @@ in {
 
           allowSubRepos = mkOption {
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Allow clients to create repositories in subdirectories of the
-              specified <option>path</option>. These can be accessed using
-              <literal>user@machine:path/to/subrepo</literal>. Note that a
-              <option>quota</option> applies to repositories independently.
+              specified {option}`path`. These can be accessed using
+              `user@machine:path/to/subrepo`. Note that a
+              {option}`quota` applies to repositories independently.
               Therefore, if this is enabled, clients can create multiple
               repositories and upload an arbitrary amount of data.
             '';
@@ -687,9 +720,9 @@ in {
           quota = mkOption {
             # See the definition of parse_file_size() in src/borg/helpers/parseformat.py
             type = with types; nullOr (strMatching "[[:digit:].]+[KMGTP]?");
-            description = ''
+            description = lib.mdDoc ''
               Storage quota for the repository. This quota is ensured for all
-              sub-repositories if <option>allowSubRepos</option> is enabled
+              sub-repositories if {option}`allowSubRepos` is enabled
               but not for the overall storage space used.
             '';
             default = null;
diff --git a/nixos/modules/services/backup/borgbackup.xml b/nixos/modules/services/backup/borgbackup.xml
index 8f623c936568..f38064f86775 100644
--- a/nixos/modules/services/backup/borgbackup.xml
+++ b/nixos/modules/services/backup/borgbackup.xml
@@ -179,7 +179,7 @@ sudo borg init --encryption=repokey-blake2  \
           mode = "repokey-blake2";
           passCommand = "cat /run/keys/borgbackup_passphrase";
         };
-        BORG_RSH = "ssh -i /run/keys/id_ed25519_borgbase";
+        environment = { BORG_RSH = "ssh -i /run/keys/id_ed25519_borgbase"; };
         compression = "auto,lzma";
         startAt = "daily";
     };
diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix
index 5e5c0bbeccca..73c4acda3936 100644
--- a/nixos/modules/services/backup/borgmatic.nix
+++ b/nixos/modules/services/backup/borgmatic.nix
@@ -4,21 +4,22 @@ with lib;
 
 let
   cfg = config.services.borgmatic;
-  cfgfile = pkgs.writeText "config.yaml" (builtins.toJSON cfg.settings);
+  settingsFormat = pkgs.formats.yaml { };
+  cfgfile = settingsFormat.generate "config.yaml" cfg.settings;
 in {
   options.services.borgmatic = {
-    enable = mkEnableOption "borgmatic";
+    enable = mkEnableOption (lib.mdDoc "borgmatic");
 
     settings = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         See https://torsion.org/borgmatic/docs/reference/configuration/
       '';
       type = types.submodule {
-        freeformType = with lib.types; attrsOf anything;
+        freeformType = settingsFormat.type;
         options.location = {
           source_directories = mkOption {
             type = types.listOf types.str;
-            description = ''
+            description = lib.mdDoc ''
               List of source directories to backup (required). Globs and
               tildes are expanded.
             '';
@@ -26,7 +27,7 @@ in {
           };
           repositories = mkOption {
             type = types.listOf types.str;
-            description = ''
+            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
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index 0c00b9344050..b6eb68cc43f1 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -1,87 +1,112 @@
 { config, pkgs, lib, ... }:
 let
-  cfg = config.services.btrbk;
-  sshEnabled = cfg.sshAccess != [ ];
-  serviceEnabled = cfg.instances != { };
-  attr2Lines = attr:
+  inherit (lib)
+    concatLists
+    concatMap
+    concatMapStringsSep
+    concatStringsSep
+    filterAttrs
+    isAttrs
+    literalExpression
+    mapAttrs'
+    mapAttrsToList
+    mkIf
+    mkOption
+    optionalString
+    sort
+    types
+    ;
+
+  # 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
-      pairs = lib.attrsets.mapAttrsToList (name: value: { inherit name value; }) attr;
-      isSubsection = value:
-        if builtins.isAttrs value then true
-        else if builtins.isString value then false
-        else throw "invalid type in btrbk config ${builtins.typeOf value}";
-      sortedPairs = lib.lists.partition (x: isSubsection x.value) pairs;
+      pairs = mapAttrsToList (name: value: { inherit name value; }) set;
+      sortedPairs = sort (a: b: prioOf a < prioOf b) pairs;
     in
-    lib.flatten (
-      # non subsections go first
-      (
-        map (pair: [ "${pair.name} ${pair.value}" ]) sortedPairs.wrong
-      )
-      ++ # subsections go last
-      (
-        map
-          (
-            pair:
-            lib.mapAttrsToList
-              (
-                childname: value:
-                  [ "${pair.name} ${childname}" ] ++ (map (x: " " + x) (attr2Lines value))
-              )
-              pair.value
-          )
-          sortedPairs.right
-      )
-    )
-  ;
+      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);
+
   addDefaults = settings: { backend = "btrfs-progs-sudo"; } // settings;
-  mkConfigFile = settings: lib.concatStringsSep "\n" (attr2Lines (addDefaults settings));
-  mkTestedConfigFile = name: settings:
-    let
-      configFile = pkgs.writeText "btrbk-${name}.conf" (mkConfigFile settings);
-    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;
+
+  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 ];
+
   options = {
     services.btrbk = {
-      extraPackages = lib.mkOption {
-        description = "Extra packages for btrbk, like compression utilities for <literal>stream_compress</literal>";
-        type = lib.types.listOf lib.types.package;
+      extraPackages = mkOption {
+        description = lib.mdDoc "Extra packages for btrbk, like compression utilities for `stream_compress`";
+        type = types.listOf types.package;
         default = [ ];
-        example = lib.literalExpression "[ pkgs.xz ]";
+        example = literalExpression "[ pkgs.xz ]";
       };
-      niceness = lib.mkOption {
-        description = "Niceness for local instances of btrbk. Also applies to remote ones connecting via ssh when positive.";
-        type = lib.types.ints.between (-20) 19;
+      niceness = mkOption {
+        description = lib.mdDoc "Niceness for local instances of btrbk. Also applies to remote ones connecting via ssh when positive.";
+        type = types.ints.between (-20) 19;
         default = 10;
       };
-      ioSchedulingClass = lib.mkOption {
-        description = "IO scheduling class for btrbk (see ionice(1) for a quick description). Applies to local instances, and remote ones connecting by ssh if set to idle.";
-        type = lib.types.enum [ "idle" "best-effort" "realtime" ];
+      ioSchedulingClass = mkOption {
+        description = lib.mdDoc "IO scheduling class for btrbk (see ionice(1) for a quick description). Applies to local instances, and remote ones connecting by ssh if set to idle.";
+        type = types.enum [ "idle" "best-effort" "realtime" ];
         default = "best-effort";
       };
-      instances = lib.mkOption {
-        description = "Set of btrbk instances. The instance named <literal>btrbk</literal> is the default one.";
-        type = with lib.types;
+      instances = mkOption {
+        description = lib.mdDoc "Set of btrbk instances. The instance named `btrbk` is the default one.";
+        type = with types;
           attrsOf (
             submodule {
               options = {
-                onCalendar = lib.mkOption {
-                  type = lib.types.str;
+                onCalendar = mkOption {
+                  type = types.nullOr types.str;
                   default = "daily";
-                  description = "How often this btrbk instance is started. See systemd.time(7) for more information about the format.";
+                  description = lib.mdDoc ''
+                    How often this btrbk instance is started. See systemd.time(7) for more information about the format.
+                    Setting it to null disables the timer, thus this instance can only be started manually.
+                  '';
                 };
-                settings = lib.mkOption {
-                  type = let t = lib.types.attrsOf (lib.types.either lib.types.str (t // { description = "instances of this type recursively"; })); in t;
+                settings = mkOption {
+                  type = let t = types.attrsOf (types.either types.str (t // { description = "instances of this type recursively"; })); in t;
                   default = { };
                   example = {
                     snapshot_preserve_min = "2d";
@@ -96,26 +121,26 @@ in
                       };
                     };
                   };
-                  description = "configuration options for btrbk. Nested attrsets translate to subsections.";
+                  description = lib.mdDoc "configuration options for btrbk. Nested attrsets translate to subsections.";
                 };
               };
             }
           );
         default = { };
       };
-      sshAccess = lib.mkOption {
-        description = "SSH keys that should be able to make or push snapshots on this system remotely with btrbk";
-        type = with lib.types; listOf (
+      sshAccess = mkOption {
+        description = lib.mdDoc "SSH keys that should be able to make or push snapshots on this system remotely with btrbk";
+        type = with types; listOf (
           submodule {
             options = {
-              key = lib.mkOption {
+              key = mkOption {
                 type = str;
-                description = "SSH public key allowed to login as user <literal>btrbk</literal> to run remote backups.";
+                description = lib.mdDoc "SSH public key allowed to login as user `btrbk` to run remote backups.";
               };
-              roles = lib.mkOption {
+              roles = mkOption {
                 type = listOf (enum [ "info" "source" "target" "delete" "snapshot" "send" "receive" ]);
                 example = [ "source" "info" "send" ];
-                description = "What actions can be performed with this SSH key. See ssh_filter_btrbk(1) for details";
+                description = lib.mdDoc "What actions can be performed with this SSH key. See ssh_filter_btrbk(1) for details";
               };
             };
           }
@@ -125,7 +150,7 @@ in
     };
 
   };
-  config = lib.mkIf (sshEnabled || serviceEnabled) {
+  config = mkIf (sshEnabled || serviceEnabled) {
     environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
     security.sudo.extraRules = [
       {
@@ -152,14 +177,14 @@ in
         (
           v:
           let
-            options = lib.concatMapStringsSep " " (x: "--" + x) v.roles;
+            options = concatMapStringsSep " " (x: "--" + x) v.roles;
             ioniceClass = {
               "idle" = 3;
               "best-effort" = 2;
               "realtime" = 1;
             }.${cfg.ioSchedulingClass};
           in
-          ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${lib.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 ${options}" ${v.key}''
         )
         cfg.sshAccess;
     };
@@ -169,15 +194,15 @@ in
       "d /var/lib/btrbk/.ssh 0700 btrbk btrbk"
       "f /var/lib/btrbk/.ssh/config 0700 btrbk btrbk - StrictHostKeyChecking=accept-new"
     ];
-    environment.etc = lib.mapAttrs'
+    environment.etc = mapAttrs'
       (
         name: instance: {
           name = "btrbk/${name}.conf";
-          value.source = mkTestedConfigFile name instance.settings;
+          value.source = mkConfigFile name instance.settings;
         }
       )
       cfg.instances;
-    systemd.services = lib.mapAttrs'
+    systemd.services = mapAttrs'
       (
         name: _: {
           name = "btrbk-${name}";
@@ -199,7 +224,7 @@ in
       )
       cfg.instances;
 
-    systemd.timers = lib.mapAttrs'
+    systemd.timers = mapAttrs'
       (
         name: instance: {
           name = "btrbk-${name}";
@@ -214,7 +239,8 @@ in
           };
         }
       )
-      cfg.instances;
+      (filterAttrs (name: instance: instance.onCalendar != null)
+        cfg.instances);
   };
 
 }
diff --git a/nixos/modules/services/backup/duplicati.nix b/nixos/modules/services/backup/duplicati.nix
index 97864c44691b..007396ebfc9b 100644
--- a/nixos/modules/services/backup/duplicati.nix
+++ b/nixos/modules/services/backup/duplicati.nix
@@ -8,12 +8,12 @@ in
 {
   options = {
     services.duplicati = {
-      enable = mkEnableOption "Duplicati";
+      enable = mkEnableOption (lib.mdDoc "Duplicati");
 
       port = mkOption {
         default = 8200;
-        type = types.int;
-        description = ''
+        type = types.port;
+        description = lib.mdDoc ''
           Port serving the web interface
         '';
       };
@@ -21,21 +21,21 @@ 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.
+          :::
         '';
       };
 
       interface = mkOption {
         default = "127.0.0.1";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Listening interface for the web UI
           Set it to "any" to listen on all available interfaces
         '';
@@ -44,7 +44,7 @@ in
       user = mkOption {
         default = "duplicati";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Duplicati runs as it's own user. It will only be able to backup world-readable files.
           Run as root with special care.
         '';
diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix
index 6949fa8b995c..05ec997ab66b 100644
--- a/nixos/modules/services/backup/duplicity.nix
+++ b/nixos/modules/services/backup/duplicity.nix
@@ -13,12 +13,12 @@ let
 in
 {
   options.services.duplicity = {
-    enable = mkEnableOption "backups with duplicity";
+    enable = mkEnableOption (lib.mdDoc "backups with duplicity");
 
     root = mkOption {
       type = types.path;
       default = "/";
-      description = ''
+      description = lib.mdDoc ''
         Root directory to backup.
       '';
     };
@@ -27,56 +27,51 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "/home" ];
-      description = ''
+      description = lib.mdDoc ''
         List of paths to include into the backups. See the FILE SELECTION
-        section in <citerefentry><refentrytitle>duplicity</refentrytitle>
-        <manvolnum>1</manvolnum></citerefentry> for details on the syntax.
+        section in {manpage}`duplicity(1)` for details on the syntax.
       '';
     };
 
     exclude = mkOption {
       type = types.listOf types.str;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         List of paths to exclude from backups. See the FILE SELECTION section in
-        <citerefentry><refentrytitle>duplicity</refentrytitle>
-        <manvolnum>1</manvolnum></citerefentry> for details on the syntax.
+        {manpage}`duplicity(1)` for details on the syntax.
       '';
     };
 
     targetUrl = mkOption {
       type = types.str;
       example = "s3://host:port/prefix";
-      description = ''
+      description = lib.mdDoc ''
         Target url to backup to. See the URL FORMAT section in
-        <citerefentry><refentrytitle>duplicity</refentrytitle>
-        <manvolnum>1</manvolnum></citerefentry> for supported urls.
+        {manpage}`duplicity(1)` for supported urls.
       '';
     };
 
     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>
-        PASSPHRASE=<replaceable>...</replaceable>
-        AWS_ACCESS_KEY_ID=<replaceable>...</replaceable>
-        AWS_SECRET_ACCESS_KEY=<replaceable>...</replaceable>
-        </programlisting>
+        {manpage}`systemd.exec(5)`. For example:
+        ```
+        PASSPHRASE=«...»
+        AWS_ACCESS_KEY_ID=«...»
+        AWS_SECRET_ACCESS_KEY=«...»
+        ```
       '';
     };
 
     frequency = mkOption {
       type = types.nullOr types.str;
       default = "daily";
-      description = ''
+      description = lib.mdDoc ''
         Run duplicity with the given frequency (see
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry> for the format).
+        {manpage}`systemd.time(7)` for the format).
         If null, do not run automatically.
       '';
     };
@@ -85,10 +80,9 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "--backend-retry-delay" "100" ];
-      description = ''
+      description = lib.mdDoc ''
         Extra command-line flags passed to duplicity. See
-        <citerefentry><refentrytitle>duplicity</refentrytitle>
-        <manvolnum>1</manvolnum></citerefentry>.
+        {manpage}`duplicity(1)`.
       '';
     };
 
@@ -96,10 +90,10 @@ in
       type = types.str;
       default = "never";
       example = "1M";
-      description = ''
-        If <literal>"never"</literal> (the default) always do incremental
+      description = lib.mdDoc ''
+        If `"never"` (the default) always do incremental
         backups (the first backup will be a full backup, of course).  If
-        <literal>"always"</literal> always do full backups.  Otherwise, this
+        `"always"` always do full backups.  Otherwise, this
         must be a string representing a duration. Full backups will be made
         when the latest full backup is older than this duration. If this is not
         the case, an incremental backup is performed.
@@ -111,7 +105,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "6M";
-        description = ''
+        description = lib.mdDoc ''
           If non-null, delete all backup sets older than the given time.  Old backup sets
           will not be deleted if backup sets newer than time depend on them.
         '';
@@ -120,7 +114,7 @@ in
         type = types.nullOr types.int;
         default = null;
         example = 2;
-        description = ''
+        description = lib.mdDoc ''
           If non-null, delete all backups sets that are older than the count:th last full
           backup (in other words, keep the last count full backups and
           associated incremental sets).
@@ -130,7 +124,7 @@ in
         type = types.nullOr types.int;
         default = null;
         example = 1;
-        description = ''
+        description = lib.mdDoc ''
           If non-null, delete incremental sets of all backups sets that are
           older than the count:th last full backup (in other words, keep only
           old full backups and not their increments).
diff --git a/nixos/modules/services/backup/mysql-backup.nix b/nixos/modules/services/backup/mysql-backup.nix
index c40a0b5abc40..289291c6bd2f 100644
--- a/nixos/modules/services/backup/mysql-backup.nix
+++ b/nixos/modules/services/backup/mysql-backup.nix
@@ -37,12 +37,12 @@ in
 
     services.mysqlBackup = {
 
-      enable = mkEnableOption "MySQL backups";
+      enable = mkEnableOption (lib.mdDoc "MySQL backups");
 
       calendar = mkOption {
         type = types.str;
         default = "01:15:00";
-        description = ''
+        description = lib.mdDoc ''
           Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second).
         '';
       };
@@ -50,7 +50,7 @@ in
       user = mkOption {
         type = types.str;
         default = defaultUser;
-        description = ''
+        description = lib.mdDoc ''
           User to be used to perform backup.
         '';
       };
@@ -58,7 +58,7 @@ in
       databases = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of database names to dump.
         '';
       };
@@ -66,7 +66,7 @@ in
       location = mkOption {
         type = types.path;
         default = "/var/backup/mysql";
-        description = ''
+        description = lib.mdDoc ''
           Location to put the gzipped MySQL database dumps.
         '';
       };
@@ -74,7 +74,7 @@ in
       singleTransaction = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create database dump in a single transaction
         '';
       };
diff --git a/nixos/modules/services/backup/postgresql-backup.nix b/nixos/modules/services/backup/postgresql-backup.nix
index 562458eb4571..d3c6f3104fc5 100644
--- a/nixos/modules/services/backup/postgresql-backup.nix
+++ b/nixos/modules/services/backup/postgresql-backup.nix
@@ -17,8 +17,8 @@ let
 
       compressCmd = getAttr cfg.compression {
         "none" = "cat";
-        "gzip" = "${pkgs.gzip}/bin/gzip -c";
-        "zstd" = "${pkgs.zstd}/bin/zstd -c";
+        "gzip" = "${pkgs.gzip}/bin/gzip -c -${toString cfg.compressionLevel}";
+        "zstd" = "${pkgs.zstd}/bin/zstd -c -${toString cfg.compressionLevel}";
       };
 
       mkSqlPath = prefix: suffix: "${cfg.location}/${db}${prefix}.sql${suffix}";
@@ -71,13 +71,13 @@ in {
 
   options = {
     services.postgresqlBackup = {
-      enable = mkEnableOption "PostgreSQL dumps";
+      enable = mkEnableOption (lib.mdDoc "PostgreSQL dumps");
 
       startAt = mkOption {
         default = "*-*-* 01:15:00";
         type = with types; either (listOf str) str;
-        description = ''
-          This option defines (see <literal>systemd.time</literal> for format) when the
+        description = lib.mdDoc ''
+          This option defines (see `systemd.time` for format) when the
           databases should be dumped.
           The default is to update at 01:15 (at night) every day.
         '';
@@ -87,10 +87,10 @@ in {
         default = cfg.databases == [];
         defaultText = literalExpression "services.postgresqlBackup.databases == []";
         type = lib.types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Backup all databases using pg_dumpall.
           This option is mutual exclusive to
-          <literal>services.postgresqlBackup.databases</literal>.
+          `services.postgresqlBackup.databases`.
           The resulting backup dump will have the name all.sql.gz.
           This option is the default if no databases are specified.
         '';
@@ -99,7 +99,7 @@ in {
       databases = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of database names to dump.
         '';
       };
@@ -107,7 +107,7 @@ in {
       location = mkOption {
         default = "/var/backup/postgresql";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path of directory where the PostgreSQL database dumps will be placed.
         '';
       };
@@ -115,9 +115,9 @@ in {
       pgdumpOptions = mkOption {
         type = types.separatedString " ";
         default = "-C";
-        description = ''
+        description = lib.mdDoc ''
           Command line options for pg_dump. This options is not used
-          if <literal>config.services.postgresqlBackup.backupAll</literal> is enabled.
+          if `config.services.postgresqlBackup.backupAll` is enabled.
           Note that config.services.postgresqlBackup.backupAll is also active,
           when no databases where specified.
         '';
@@ -126,20 +126,37 @@ in {
       compression = mkOption {
         type = types.enum ["none" "gzip" "zstd"];
         default = "gzip";
-        description = ''
+        description = lib.mdDoc ''
           The type of compression to use on the generated database dump.
         '';
       };
+
+      compressionLevel = mkOption {
+        type = types.ints.between 1 19;
+        default = 6;
+        description = lib.mdDoc ''
+          The compression level used when compression is enabled.
+          gzip accepts levels 1 to 9. zstd accepts levels 1 to 19.
+        '';
+      };
     };
 
   };
 
   config = mkMerge [
     {
-      assertions = [{
-        assertion = cfg.backupAll -> cfg.databases == [];
-        message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
-      }];
+      assertions = [
+        {
+          assertion = cfg.backupAll -> cfg.databases == [];
+          message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
+        }
+        {
+          assertion = cfg.compression == "none" ||
+            (cfg.compression == "gzip" && cfg.compressionLevel >= 1 && cfg.compressionLevel <= 9) ||
+            (cfg.compression == "zstd" && cfg.compressionLevel >= 1 && cfg.compressionLevel <= 19);
+          message = "config.services.postgresqlBackup.compressionLevel must be set between 1 and 9 for gzip and 1 and 19 for zstd";
+        }
+      ];
     }
     (mkIf cfg.enable {
       systemd.tmpfiles.rules = [
diff --git a/nixos/modules/services/backup/postgresql-wal-receiver.nix b/nixos/modules/services/backup/postgresql-wal-receiver.nix
index 32643adfdaea..01fd57f5c506 100644
--- a/nixos/modules/services/backup/postgresql-wal-receiver.nix
+++ b/nixos/modules/services/backup/postgresql-wal-receiver.nix
@@ -8,7 +8,7 @@ let
       postgresqlPackage = mkOption {
         type = types.package;
         example = literalExpression "pkgs.postgresql_11";
-        description = ''
+        description = lib.mdDoc ''
           PostgreSQL package to use.
         '';
       };
@@ -16,7 +16,7 @@ let
       directory = mkOption {
         type = types.path;
         example = literalExpression "/mnt/pg_wal/main/";
-        description = ''
+        description = lib.mdDoc ''
           Directory to write the output to.
         '';
       };
@@ -24,7 +24,7 @@ let
       statusInterval = mkOption {
         type = types.int;
         default = 10;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the number of seconds between status packets sent back to the server.
           This allows for easier monitoring of the progress from server.
           A value of zero disables the periodic status updates completely,
@@ -36,27 +36,27 @@ let
         type = types.str;
         default = "";
         example = "some_slot_name";
-        description = ''
-          Require <command>pg_receivewal</command> to use an existing replication slot (see
-          <link xlink:href="https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS">Section 26.2.6 of the PostgreSQL manual</link>).
-          When this option is used, <command>pg_receivewal</command> will report a flush position to the server,
+        description = lib.mdDoc ''
+          Require {command}`pg_receivewal` to use an existing replication slot (see
+          [Section 26.2.6 of the PostgreSQL manual](https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS)).
+          When this option is used, {command}`pg_receivewal` will report a flush position to the server,
           indicating when each segment has been synchronized to disk so that the server can remove that segment if it is not otherwise needed.
 
-          When the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
+          When the replication client of {command}`pg_receivewal` is configured on the server as a synchronous standby,
           then using a replication slot will report the flush position to the server, but only when a WAL file is closed.
           Therefore, that configuration will cause transactions on the primary to wait for a long time and effectively not work satisfactorily.
-          The option <option>synchronous</option> must be specified in addition to make this work correctly.
+          The option {option}`synchronous` must be specified in addition to make this work correctly.
         '';
       };
 
       synchronous = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Flush the WAL data to disk immediately after it has been received.
-          Also send a status packet back to the server immediately after flushing, regardless of <option>statusInterval</option>.
+          Also send a status packet back to the server immediately after flushing, regardless of {option}`statusInterval`.
 
-          This option should be specified if the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
+          This option should be specified if the replication client of {command}`pg_receivewal` is configured on the server as a synchronous standby,
           to ensure that timely feedback is sent to the server.
         '';
       };
@@ -64,10 +64,10 @@ let
       compress = mkOption {
         type = types.ints.between 0 9;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Enables gzip compression of write-ahead logs, and specifies the compression level
-          (<literal>0</literal> through <literal>9</literal>, <literal>0</literal> being no compression and <literal>9</literal> being best compression).
-          The suffix <literal>.gz</literal> will automatically be added to all filenames.
+          (`0` through `9`, `0` being no compression and `9` being best compression).
+          The suffix `.gz` will automatically be added to all filenames.
 
           This option requires PostgreSQL >= 10.
         '';
@@ -76,11 +76,11 @@ let
       connection = mkOption {
         type = types.str;
         example = "postgresql://user@somehost";
-        description = ''
+        description = lib.mdDoc ''
           Specifies parameters used to connect to the server, as a connection string.
-          See <link xlink:href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING">Section 34.1.1 of the PostgreSQL manual</link> for more information.
+          See [Section 34.1.1 of the PostgreSQL manual](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) for more information.
 
-          Because <command>pg_receivewal</command> doesn't connect to any particular database in the cluster,
+          Because {command}`pg_receivewal` doesn't connect to any particular database in the cluster,
           database name in the connection string will be ignored.
         '';
       };
@@ -93,8 +93,8 @@ let
             "--no-sync"
           ]
         '';
-        description = ''
-          A list of extra arguments to pass to the <command>pg_receivewal</command> command.
+        description = lib.mdDoc ''
+          A list of extra arguments to pass to the {command}`pg_receivewal` command.
         '';
       };
 
@@ -107,9 +107,9 @@ let
             PGSSLMODE = "require";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Environment variables passed to the service.
-          Usable parameters are listed in <link xlink:href="https://www.postgresql.org/docs/current/libpq-envars.html">Section 34.14 of the PostgreSQL manual</link>.
+          Usable parameters are listed in [Section 34.14 of the PostgreSQL manual](https://www.postgresql.org/docs/current/libpq-envars.html).
         '';
       };
     };
@@ -131,10 +131,10 @@ in {
             };
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           PostgreSQL WAL receivers.
-          Stream write-ahead logs from a PostgreSQL server using <command>pg_receivewal</command> (formerly <command>pg_receivexlog</command>).
-          See <link xlink:href="https://www.postgresql.org/docs/current/app-pgreceivewal.html">the man page</link> for more information.
+          Stream write-ahead logs from a PostgreSQL server using {command}`pg_receivewal` (formerly {command}`pg_receivexlog`).
+          See [the man page](https://www.postgresql.org/docs/current/app-pgreceivewal.html) for more information.
         '';
       };
     };
diff --git a/nixos/modules/services/backup/restic-rest-server.nix b/nixos/modules/services/backup/restic-rest-server.nix
index 4717119f178a..37a6150c99d3 100644
--- a/nixos/modules/services/backup/restic-rest-server.nix
+++ b/nixos/modules/services/backup/restic-rest-server.nix
@@ -9,25 +9,25 @@ 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";
       example = "127.0.0.1:8080";
       type = types.str;
-      description = "Listen on a specific IP address and port.";
+      description = lib.mdDoc "Listen on a specific IP address and port.";
     };
 
     dataDir = mkOption {
       default = "/var/lib/restic";
       type = types.path;
-      description = "The directory for storing the restic repository.";
+      description = lib.mdDoc "The directory for storing the restic repository.";
     };
 
     appendOnly = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Enable append only mode.
         This mode allows creation of new backups but prevents deletion and modification of existing backups.
         This can be useful when backing up systems that have a potential of being hacked.
@@ -37,7 +37,7 @@ in
     privateRepos = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Enable private repos.
         Grants access only when a subdirectory with the same name as the user is specified in the repository URL.
       '';
@@ -46,13 +46,13 @@ in
     prometheus = mkOption {
       default = false;
       type = types.bool;
-      description = "Enable Prometheus metrics at /metrics.";
+      description = lib.mdDoc "Enable Prometheus metrics at /metrics.";
     };
 
     extraFlags = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Extra commandline options to pass to Restic REST server.
       '';
     };
@@ -61,7 +61,7 @@ in
       default = pkgs.restic-rest-server;
       defaultText = literalExpression "pkgs.restic-rest-server";
       type = types.package;
-      description = "Restic REST server package to use.";
+      description = lib.mdDoc "Restic REST server package to use.";
     };
   };
 
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 8ff8e31864be..869ed5d9976c 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -8,14 +8,14 @@ let
 in
 {
   options.services.restic.backups = mkOption {
-    description = ''
+    description = lib.mdDoc ''
       Periodic backups to create with Restic.
     '';
     type = types.attrsOf (types.submodule ({ config, name, ... }: {
       options = {
         passwordFile = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Read the repository password from a file.
           '';
           example = "/etc/nixos/restic-password";
@@ -26,7 +26,7 @@ in
           # added on 2021-08-28, s3CredentialsFile should
           # be removed in the future (+ remember the warning)
           default = config.s3CredentialsFile;
-          description = ''
+          description = lib.mdDoc ''
             file containing the credentials to access the repository, in the
             format of an EnvironmentFile as described by systemd.exec(5)
           '';
@@ -35,7 +35,7 @@ in
         s3CredentialsFile = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             file containing the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
             for an S3-hosted repository, in the format of an EnvironmentFile
             as described by systemd.exec(5)
@@ -45,13 +45,13 @@ in
         rcloneOptions = mkOption {
           type = with types; nullOr (attrsOf (oneOf [ str bool ]));
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Options to pass to rclone to control its behavior.
-            See <link xlink:href="https://rclone.org/docs/#options"/> for
+            See <https://rclone.org/docs/#options> for
             available options. When specifying option names, strip the
-            leading <literal>--</literal>. To set a flag such as
-            <literal>--drive-use-trash</literal>, which does not take a value,
-            set the value to the Boolean <literal>true</literal>.
+            leading `--`. To set a flag such as
+            `--drive-use-trash`, which does not take a value,
+            set the value to the Boolean `true`.
           '';
           example = {
             bwlimit = "10M";
@@ -62,16 +62,16 @@ in
         rcloneConfig = mkOption {
           type = with types; nullOr (attrsOf (oneOf [ str bool ]));
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Configuration for the rclone remote being used for backup.
             See the remote's specific options under rclone's docs at
-            <link xlink:href="https://rclone.org/docs/"/>. When specifying
+            <https://rclone.org/docs/>. When specifying
             option names, use the "config" name specified in the docs.
-            For example, to set <literal>--b2-hard-delete</literal> for a B2
-            remote, use <literal>hard_delete = true</literal> in the
+            For example, to set `--b2-hard-delete` for a B2
+            remote, use `hard_delete = true` in the
             attribute set.
             Warning: Secrets set in here will be world-readable in the Nix
-            store! Consider using the <literal>rcloneConfigFile</literal>
+            store! Consider using the `rcloneConfigFile`
             option instead to specify secret values separately. Note that
             options set here will override those set in the config file.
           '';
@@ -86,27 +86,36 @@ in
         rcloneConfigFile = mkOption {
           type = with types; nullOr path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Path to the file containing rclone configuration. This file
             must contain configuration for the remote specified in this backup
             set and also must be readable by root. Options set in
-            <literal>rcloneConfig</literal> will override those set in this
+            `rcloneConfig` will override those set in this
             file.
           '';
         };
 
         repository = mkOption {
-          type = types.str;
-          description = ''
+          type = with types; nullOr str;
+          default = null;
+          description = lib.mdDoc ''
             repository to backup to.
           '';
           example = "sftp:backup@192.168.1.100:/backups/${name}";
         };
 
+        repositoryFile = mkOption {
+          type = with types; nullOr path;
+          default = null;
+          description = lib.mdDoc ''
+            Path to the file containing the repository location to backup to.
+          '';
+        };
+
         paths = mkOption {
           type = types.nullOr (types.listOf types.str);
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Which paths to backup.  If null or an empty array, no
             backup command will be run.  This can be used to create a
             prune-only job.
@@ -122,7 +131,7 @@ in
           default = {
             OnCalendar = "daily";
           };
-          description = ''
+          description = lib.mdDoc ''
             When to run the backup. See man systemd.timer for details.
           '';
           example = {
@@ -134,7 +143,7 @@ in
         user = mkOption {
           type = types.str;
           default = "root";
-          description = ''
+          description = lib.mdDoc ''
             As which user the backup should run.
           '';
           example = "postgresql";
@@ -142,8 +151,8 @@ in
 
         extraBackupArgs = mkOption {
           type = types.listOf types.str;
-          default = [];
-          description = ''
+          default = [ ];
+          description = lib.mdDoc ''
             Extra arguments passed to restic backup.
           '';
           example = [
@@ -153,8 +162,8 @@ in
 
         extraOptions = mkOption {
           type = types.listOf types.str;
-          default = [];
-          description = ''
+          default = [ ];
+          description = lib.mdDoc ''
             Extra extended options to be passed to the restic --option flag.
           '';
           example = [
@@ -165,19 +174,19 @@ in
         initialize = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Create the repository if it doesn't exist.
           '';
         };
 
         pruneOpts = mkOption {
           type = types.listOf types.str;
-          default = [];
-          description = ''
-            A list of options (--keep-* et al.) for 'restic forget
+          default = [ ];
+          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"
@@ -187,19 +196,56 @@ 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;
-          description = ''
+          description = lib.mdDoc ''
             A script that produces a list of files to back up.  The
             results of this command are given to the '--files-from'
             option.
           '';
           example = "find /home/matt/git -type d -name .git";
         };
+
+        backupPrepareCommand = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = lib.mdDoc ''
+            A script that must run before starting the backup process.
+          '';
+        };
+
+        backupCleanupCommand = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = lib.mdDoc ''
+            A script that must run after finishing the backup process.
+          '';
+        };
+
+        package = mkOption {
+          type = types.package;
+          default = pkgs.restic;
+          defaultText = literalExpression "pkgs.restic";
+          description = lib.mdDoc ''
+            Restic package to use.
+          '';
+        };
       };
     }));
-    default = {};
+    default = { };
     example = {
       localbackup = {
         paths = [ "/home" ];
@@ -225,66 +271,85 @@ in
   config = {
     warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups);
     systemd.services =
-      mapAttrs' (name: backup:
-        let
-          extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
-          resticCmd = "${pkgs.restic}/bin/restic${extraOptions}";
-          filesFromTmpFile = "/run/restic-backups-${name}/includes";
-          backupPaths = if (backup.dynamicFilesFrom == null)
-                        then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
-                        else "--files-from ${filesFromTmpFile}";
-          pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
-            ( resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts) )
-            ( resticCmd + " check" )
-          ];
-          # Helper functions for rclone remotes
-          rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
-          rcloneAttrToOpt = v: "RCLONE_" + toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
-          rcloneAttrToConf = v: "RCLONE_CONFIG_" + toUpper (rcloneRemoteName + "_" + v);
-          toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
-        in nameValuePair "restic-backups-${name}" ({
-          environment = {
-            RESTIC_PASSWORD_FILE = backup.passwordFile;
-            RESTIC_REPOSITORY = backup.repository;
-          } // optionalAttrs (backup.rcloneOptions != null) (mapAttrs' (name: value:
-            nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
-          ) backup.rcloneOptions) // optionalAttrs (backup.rcloneConfigFile != null) {
-            RCLONE_CONFIG = backup.rcloneConfigFile;
-          } // optionalAttrs (backup.rcloneConfig != null) (mapAttrs' (name: value:
-            nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
-          ) backup.rcloneConfig);
-          path = [ pkgs.openssh ];
-          restartIfChanged = false;
-          serviceConfig = {
-            Type = "oneshot";
-            ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ])
-                        ++ pruneCmd;
-            User = backup.user;
-            RuntimeDirectory = "restic-backups-${name}";
-            CacheDirectory = "restic-backups-${name}";
-            CacheDirectoryMode = "0700";
-          } // optionalAttrs (backup.environmentFile != null) {
-            EnvironmentFile = backup.environmentFile;
-          };
-        } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null) {
-          preStart = ''
-            ${optionalString (backup.initialize) ''
-              ${resticCmd} snapshots || ${resticCmd} init
-            ''}
-            ${optionalString (backup.dynamicFilesFrom != null) ''
-              ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile}
-            ''}
-          '';
-        } // optionalAttrs (backup.dynamicFilesFrom != null) {
-          postStart = ''
-            rm ${filesFromTmpFile}
-          '';
-        })
-      ) config.services.restic.backups;
+      mapAttrs'
+        (name: backup:
+          let
+            extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
+            resticCmd = "${backup.package}/bin/restic${extraOptions}";
+            filesFromTmpFile = "/run/restic-backups-${name}/includes";
+            backupPaths =
+              if (backup.dynamicFilesFrom == null)
+              then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
+              else "--files-from ${filesFromTmpFile}";
+            pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
+              (resticCmd + " forget --prune --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.pruneOpts))
+              (resticCmd + " check --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.checkOpts))
+            ];
+            # Helper functions for rclone remotes
+            rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
+            rcloneAttrToOpt = v: "RCLONE_" + toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
+            rcloneAttrToConf = v: "RCLONE_CONFIG_" + toUpper (rcloneRemoteName + "_" + v);
+            toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
+          in
+          nameValuePair "restic-backups-${name}" ({
+            environment = {
+              RESTIC_PASSWORD_FILE = backup.passwordFile;
+              RESTIC_REPOSITORY = backup.repository;
+              RESTIC_REPOSITORY_FILE = backup.repositoryFile;
+            } // optionalAttrs (backup.rcloneOptions != null) (mapAttrs'
+              (name: value:
+                nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
+              )
+              backup.rcloneOptions) // optionalAttrs (backup.rcloneConfigFile != null) {
+              RCLONE_CONFIG = backup.rcloneConfigFile;
+            } // optionalAttrs (backup.rcloneConfig != null) (mapAttrs'
+              (name: value:
+                nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
+              )
+              backup.rcloneConfig);
+            path = [ pkgs.openssh ];
+            restartIfChanged = false;
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ])
+                ++ pruneCmd;
+              User = backup.user;
+              RuntimeDirectory = "restic-backups-${name}";
+              CacheDirectory = "restic-backups-${name}";
+              CacheDirectoryMode = "0700";
+            } // optionalAttrs (backup.environmentFile != null) {
+              EnvironmentFile = backup.environmentFile;
+            };
+          } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null || backup.backupPrepareCommand != null) {
+            preStart = ''
+              ${optionalString (backup.backupPrepareCommand != null) ''
+                ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
+              ''}
+              ${optionalString (backup.initialize) ''
+                ${resticCmd} snapshots || ${resticCmd} init
+              ''}
+              ${optionalString (backup.dynamicFilesFrom != null) ''
+                ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile}
+              ''}
+            '';
+          } // optionalAttrs (backup.dynamicFilesFrom != null || backup.backupCleanupCommand != null) {
+            postStop = ''
+              ${optionalString (backup.backupCleanupCommand != null) ''
+                ${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
+              ''}
+              ${optionalString (backup.dynamicFilesFrom != null) ''
+                rm ${filesFromTmpFile}
+              ''}
+            '';
+          })
+        )
+        config.services.restic.backups;
     systemd.timers =
-      mapAttrs' (name: backup: nameValuePair "restic-backups-${name}" {
-        wantedBy = [ "timers.target" ];
-        timerConfig = backup.timerConfig;
-      }) config.services.restic.backups;
+      mapAttrs'
+        (name: backup: nameValuePair "restic-backups-${name}" {
+          wantedBy = [ "timers.target" ];
+          timerConfig = backup.timerConfig;
+        })
+        config.services.restic.backups;
   };
 }
diff --git a/nixos/modules/services/backup/rsnapshot.nix b/nixos/modules/services/backup/rsnapshot.nix
index 6635a51ec2c6..0b9bb60af0ea 100644
--- a/nixos/modules/services/backup/rsnapshot.nix
+++ b/nixos/modules/services/backup/rsnapshot.nix
@@ -22,9 +22,9 @@ in
 {
   options = {
     services.rsnapshot = {
-      enable = mkEnableOption "rsnapshot backups";
+      enable = mkEnableOption (lib.mdDoc "rsnapshot backups");
       enableManualRsnapshot = mkOption {
-        description = "Whether to enable manual usage of the rsnapshot command with this module.";
+        description = lib.mdDoc "Whether to enable manual usage of the rsnapshot command with this module.";
         default = true;
         type = types.bool;
       };
@@ -37,7 +37,7 @@ in
           backup	/home/	localhost/
         '';
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           rsnapshot configuration option in addition to the defaults from
           rsnapshot and this module.
 
@@ -53,7 +53,7 @@ in
         default = {};
         example = { hourly = "0 * * * *"; daily = "50 21 * * *"; };
         type = types.attrsOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Periodicity at which intervals should be run by cron.
           Note that the intervals also have to exist in configuration
           as retain options.
diff --git a/nixos/modules/services/backup/sanoid.nix b/nixos/modules/services/backup/sanoid.nix
index 5eb031b2e9f0..a51708170fbf 100644
--- a/nixos/modules/services/backup/sanoid.nix
+++ b/nixos/modules/services/backup/sanoid.nix
@@ -12,37 +12,37 @@ let
 
   commonOptions = {
     hourly = mkOption {
-      description = "Number of hourly snapshots.";
+      description = lib.mdDoc "Number of hourly snapshots.";
       type = with types; nullOr ints.unsigned;
       default = null;
     };
 
     daily = mkOption {
-      description = "Number of daily snapshots.";
+      description = lib.mdDoc "Number of daily snapshots.";
       type = with types; nullOr ints.unsigned;
       default = null;
     };
 
     monthly = mkOption {
-      description = "Number of monthly snapshots.";
+      description = lib.mdDoc "Number of monthly snapshots.";
       type = with types; nullOr ints.unsigned;
       default = null;
     };
 
     yearly = mkOption {
-      description = "Number of yearly snapshots.";
+      description = lib.mdDoc "Number of yearly snapshots.";
       type = with types; nullOr ints.unsigned;
       default = null;
     };
 
     autoprune = mkOption {
-      description = "Whether to automatically prune old snapshots.";
+      description = lib.mdDoc "Whether to automatically prune old snapshots.";
       type = with types; nullOr bool;
       default = null;
     };
 
     autosnap = mkOption {
-      description = "Whether to automatically take snapshots.";
+      description = lib.mdDoc "Whether to automatically take snapshots.";
       type = with types; nullOr bool;
       default = null;
     };
@@ -50,7 +50,7 @@ let
 
   datasetOptions = rec {
     use_template = mkOption {
-      description = "Names of the templates to use for this dataset.";
+      description = lib.mdDoc "Names of the templates to use for this dataset.";
       type = types.listOf (types.str // {
         check = (types.enum (attrNames cfg.templates)).check;
         description = "configured template name";
@@ -60,9 +60,9 @@ let
     useTemplate = use_template;
 
     recursive = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to recursively snapshot dataset children.
-        You can also set this to <literal>"zfs"</literal> to handle datasets
+        You can also set this to `"zfs"` to handle datasets
         recursively in an atomic way without the possibility to
         override settings for child datasets.
       '';
@@ -71,7 +71,7 @@ let
     };
 
     process_children_only = mkOption {
-      description = "Whether to only snapshot child datasets if recursing.";
+      description = lib.mdDoc "Whether to only snapshot child datasets if recursing.";
       type = types.bool;
       default = false;
     };
@@ -112,18 +112,17 @@ in
   # Interface
 
   options.services.sanoid = {
-    enable = mkEnableOption "Sanoid ZFS snapshotting service";
+    enable = mkEnableOption (lib.mdDoc "Sanoid ZFS snapshotting service");
 
     interval = mkOption {
       type = types.str;
       default = "hourly";
       example = "daily";
-      description = ''
+      description = lib.mdDoc ''
         Run sanoid at this interval. The default is to run hourly.
 
         The format is described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
+        {manpage}`systemd.time(7)`.
       '';
     };
 
@@ -131,11 +130,11 @@ 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 = "Datasets to snapshot.";
+      description = lib.mdDoc "Datasets to snapshot.";
     };
 
     templates = mkOption {
@@ -144,14 +143,14 @@ in
         options = commonOptions;
       });
       default = { };
-      description = "Templates for datasets.";
+      description = lib.mdDoc "Templates for datasets.";
     };
 
     settings = mkOption {
       type = types.attrsOf datasetSettingsType;
-      description = ''
+      description = lib.mdDoc ''
         Free-form settings written directly to the config file. See
-        <link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/>
+        <https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf>
         for allowed values.
       '';
     };
@@ -160,9 +159,9 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "--verbose" "--readonly" "--debug" ];
-      description = ''
+      description = lib.mdDoc ''
         Extra arguments to pass to sanoid. See
-        <link xlink:href="https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options"/>
+        <https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options>
         for allowed options.
       '';
     };
diff --git a/nixos/modules/services/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix
index 4df10f5ee02b..6188f1094630 100644
--- a/nixos/modules/services/backup/syncoid.nix
+++ b/nixos/modules/services/backup/syncoid.nix
@@ -16,11 +16,11 @@ let
     lib.concatMapStrings (s: if lib.isList s then "-" else s)
       (builtins.split "[^a-zA-Z0-9_.\\-]+" name);
 
-  # Function to build "zfs allow" commands for the filesystems we've
-  # delegated permissions to. It also checks if the target dataset
-  # exists before delegating permissions, if it doesn't exist we
-  # delegate it to the parent dataset. This should solve the case of
-  # provisoning new datasets.
+  # Function to build "zfs allow" commands for the filesystems we've delegated
+  # permissions to. It also checks if the target dataset exists before
+  # delegating permissions, if it doesn't exist we delegate it to the parent
+  # dataset (if it exists). This should solve the case of provisoning new
+  # datasets.
   buildAllowCommand = permissions: dataset: (
     "-+${pkgs.writeShellScript "zfs-allow-${dataset}" ''
       # Here we explicitly use the booted system to guarantee the stable API needed by ZFS
@@ -38,15 +38,17 @@ let
           (concatStringsSep "," permissions)
           dataset
         ]}
-      else
-        ${lib.escapeShellArgs [
-          "/run/booted-system/sw/bin/zfs"
-          "allow"
-          cfg.user
-          (concatStringsSep "," permissions)
-          # Remove the last part of the path
-          (builtins.dirOf dataset)
-        ]}
+      ${lib.optionalString ((builtins.dirOf dataset) != ".") ''
+        else
+          ${lib.escapeShellArgs [
+            "/run/booted-system/sw/bin/zfs"
+            "allow"
+            cfg.user
+            (concatStringsSep "," permissions)
+            # Remove the last part of the path
+            (builtins.dirOf dataset)
+          ]}
+      ''}
       fi
     ''}"
   );
@@ -67,14 +69,14 @@ let
         (concatStringsSep "," permissions)
         dataset
       ]}
-      ${lib.escapeShellArgs [
+      ${lib.optionalString ((builtins.dirOf dataset) != ".") (lib.escapeShellArgs [
         "/run/booted-system/sw/bin/zfs"
         "unallow"
         cfg.user
         (concatStringsSep "," permissions)
         # Remove the last part of the path
         (builtins.dirOf dataset)
-      ]}
+      ])}
     ''}"
   );
 in
@@ -83,18 +85,17 @@ in
   # Interface
 
   options.services.syncoid = {
-    enable = mkEnableOption "Syncoid ZFS synchronization service";
+    enable = mkEnableOption (lib.mdDoc "Syncoid ZFS synchronization service");
 
     interval = mkOption {
       type = types.str;
       default = "hourly";
       example = "*-*-* *:15:00";
-      description = ''
+      description = lib.mdDoc ''
         Run syncoid at this interval. The default is to run hourly.
 
         The format is described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
+        {manpage}`systemd.time(7)`.
       '';
     };
 
@@ -102,7 +103,7 @@ in
       type = types.str;
       default = "syncoid";
       example = "backup";
-      description = ''
+      description = lib.mdDoc ''
         The user for the service. ZFS privilege delegation will be
         automatically configured for any local pools used by syncoid if this
         option is set to a user other than root. The user will be given the
@@ -116,7 +117,7 @@ in
       type = types.str;
       default = "syncoid";
       example = "backup";
-      description = "The group for the service.";
+      description = lib.mdDoc "The group for the service.";
     };
 
     sshKey = mkOption {
@@ -124,7 +125,7 @@ in
       # Prevent key from being copied to store
       apply = mapNullable toString;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         SSH private key file to use to login to the remote system. Can be
         overridden in individual commands.
       '';
@@ -134,10 +135,10 @@ in
       type = types.listOf types.str;
       # Permissions snapshot and destroy are in case --no-sync-snap is not used
       default = [ "bookmark" "hold" "send" "snapshot" "destroy" ];
-      description = ''
-        Permissions granted for the <option>services.syncoid.user</option> user
+      description = lib.mdDoc ''
+        Permissions granted for the {option}`services.syncoid.user` user
         for local source datasets. See
-        <link xlink:href="https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html"/>
+        <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
         for available permissions.
       '';
     };
@@ -146,13 +147,13 @@ in
       type = types.listOf types.str;
       default = [ "change-key" "compression" "create" "mount" "mountpoint" "receive" "rollback" ];
       example = [ "create" "mount" "receive" "rollback" ];
-      description = ''
-        Permissions granted for the <option>services.syncoid.user</option> user
+      description = lib.mdDoc ''
+        Permissions granted for the {option}`services.syncoid.user` user
         for local target datasets. See
-        <link xlink:href="https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html"/>
+        <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
         for available permissions.
-        Make sure to include the <literal>change-key</literal> permission if you send raw encrypted datasets,
-        the <literal>compression</literal> permission if you send raw compressed datasets, and so on.
+        Make sure to include the `change-key` permission if you send raw encrypted datasets,
+        the `compression` permission if you send raw compressed datasets, and so on.
         For remote target datasets you'll have to set your remote user permissions by yourself.
       '';
     };
@@ -161,10 +162,10 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "--no-sync-snap" ];
-      description = ''
+      description = lib.mdDoc ''
         Arguments to add to every syncoid command, unless disabled for that
         command. See
-        <link xlink:href="https://github.com/jimsalterjrs/sanoid/#syncoid-command-line-options"/>
+        <https://github.com/jimsalterjrs/sanoid/#syncoid-command-line-options>
         for available options.
       '';
     };
@@ -172,7 +173,7 @@ in
     service = mkOption {
       type = types.attrs;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Systemd configuration common to all syncoid services.
       '';
     };
@@ -183,7 +184,7 @@ in
           source = mkOption {
             type = types.str;
             example = "pool/dataset";
-            description = ''
+            description = lib.mdDoc ''
               Source ZFS dataset. Can be either local or remote. Defaults to
               the attribute name.
             '';
@@ -192,45 +193,45 @@ in
           target = mkOption {
             type = types.str;
             example = "user@server:pool/dataset";
-            description = ''
+            description = lib.mdDoc ''
               Target ZFS dataset. Can be either local
-              (<replaceable>pool/dataset</replaceable>) or remote
-              (<replaceable>user@server:pool/dataset</replaceable>).
+              («pool/dataset») or remote
+              («user@server:pool/dataset»).
             '';
           };
 
-          recursive = mkEnableOption ''the transfer of child datasets'';
+          recursive = mkEnableOption (lib.mdDoc ''the transfer of child datasets'');
 
           sshKey = mkOption {
             type = types.nullOr types.path;
             # Prevent key from being copied to store
             apply = mapNullable toString;
-            description = ''
+            description = lib.mdDoc ''
               SSH private key file to use to login to the remote system.
-              Defaults to <option>services.syncoid.sshKey</option> option.
+              Defaults to {option}`services.syncoid.sshKey` option.
             '';
           };
 
           localSourceAllow = mkOption {
             type = types.listOf types.str;
-            description = ''
-              Permissions granted for the <option>services.syncoid.user</option> user
+            description = lib.mdDoc ''
+              Permissions granted for the {option}`services.syncoid.user` user
               for local source datasets. See
-              <link xlink:href="https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html"/>
+              <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
               for available permissions.
-              Defaults to <option>services.syncoid.localSourceAllow</option> option.
+              Defaults to {option}`services.syncoid.localSourceAllow` option.
             '';
           };
 
           localTargetAllow = mkOption {
             type = types.listOf types.str;
-            description = ''
-              Permissions granted for the <option>services.syncoid.user</option> user
+            description = lib.mdDoc ''
+              Permissions granted for the {option}`services.syncoid.user` user
               for local target datasets. See
-              <link xlink:href="https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html"/>
+              <https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html>
               for available permissions.
-              Make sure to include the <literal>change-key</literal> permission if you send raw encrypted datasets,
-              the <literal>compression</literal> permission if you send raw compressed datasets, and so on.
+              Make sure to include the `change-key` permission if you send raw encrypted datasets,
+              the `compression` permission if you send raw compressed datasets, and so on.
               For remote target datasets you'll have to set your remote user permissions by yourself.
             '';
           };
@@ -239,7 +240,7 @@ in
             type = types.separatedString " ";
             default = "";
             example = "Lc e";
-            description = ''
+            description = lib.mdDoc ''
               Advanced options to pass to zfs send. Options are specified
               without their leading dashes and separated by spaces.
             '';
@@ -249,7 +250,7 @@ in
             type = types.separatedString " ";
             default = "";
             example = "ux recordsize o compression=lz4";
-            description = ''
+            description = lib.mdDoc ''
               Advanced options to pass to zfs recv. Options are specified
               without their leading dashes and separated by spaces.
             '';
@@ -258,7 +259,7 @@ in
           useCommonArgs = mkOption {
             type = types.bool;
             default = true;
-            description = ''
+            description = lib.mdDoc ''
               Whether to add the configured common arguments to this command.
             '';
           };
@@ -266,7 +267,7 @@ in
           service = mkOption {
             type = types.attrs;
             default = { };
-            description = ''
+            description = lib.mdDoc ''
               Systemd configuration specific to this syncoid service.
             '';
           };
@@ -275,7 +276,7 @@ in
             type = types.listOf types.str;
             default = [ ];
             example = [ "--sshport 2222" ];
-            description = "Extra syncoid arguments for this command.";
+            description = lib.mdDoc "Extra syncoid arguments for this command.";
           };
         };
         config = {
@@ -291,7 +292,7 @@ in
           "pool/test".target = "root@target:pool/test";
         }
       '';
-      description = "Syncoid commands to run.";
+      description = lib.mdDoc "Syncoid commands to run.";
     };
   };
 
diff --git a/nixos/modules/services/backup/tarsnap.nix b/nixos/modules/services/backup/tarsnap.nix
index 9b5fd90012e0..b34aa3ff50dd 100644
--- a/nixos/modules/services/backup/tarsnap.nix
+++ b/nixos/modules/services/backup/tarsnap.nix
@@ -30,15 +30,15 @@ in
 
   options = {
     services.tarsnap = {
-      enable = mkEnableOption "periodic tarsnap backups";
+      enable = mkEnableOption (lib.mdDoc "periodic tarsnap backups");
 
       keyfile = mkOption {
         type = types.str;
         default = "/root/tarsnap.key";
-        description = ''
+        description = lib.mdDoc ''
           The keyfile which associates this machine with your tarsnap
           account.
-          Create the keyfile with <command>tarsnap-keygen</command>.
+          Create the keyfile with {command}`tarsnap-keygen`.
 
           Note that each individual archive (specified below) may also have its
           own individual keyfile specified. Tarsnap does not allow multiple
@@ -47,11 +47,11 @@ in
           archives specified, you should either spread out your backups to be
           far apart, or specify a separate key for each archive. By default
           every archive defaults to using
-          <literal>"/root/tarsnap.key"</literal>.
+          `"/root/tarsnap.key"`.
 
           It's recommended for backups that you generate a key for every archive
-          using <literal>tarsnap-keygen(1)</literal>, and then generate a
-          write-only tarsnap key using <literal>tarsnap-keymgmt(1)</literal>,
+          using `tarsnap-keygen(1)`, and then generate a
+          write-only tarsnap key using `tarsnap-keymgmt(1)`,
           and keep your master key(s) for a particular machine off-site.
 
           The keyfile name should be given as a string and not a path, to
@@ -67,18 +67,18 @@ in
                 type = types.str;
                 default = gcfg.keyfile;
                 defaultText = literalExpression "config.${opt.keyfile}";
-                description = ''
+                description = lib.mdDoc ''
                   Set a specific keyfile for this archive. This defaults to
-                  <literal>"/root/tarsnap.key"</literal> if left unspecified.
+                  `"/root/tarsnap.key"` if left unspecified.
 
                   Use this option if you want to run multiple backups
                   concurrently - each archive must have a unique key. You can
                   generate a write-only key derived from your master key (which
-                  is recommended) using <literal>tarsnap-keymgmt(1)</literal>.
+                  is recommended) using `tarsnap-keymgmt(1)`.
 
                   Note: every archive must have an individual master key. You
                   must generate multiple keys with
-                  <literal>tarsnap-keygen(1)</literal>, and then generate write
+                  `tarsnap-keygen(1)`, and then generate write
                   only keys from those.
 
                   The keyfile name should be given as a string and not a path, to
@@ -92,47 +92,47 @@ in
                 defaultText = literalExpression ''
                   "/var/cache/tarsnap/''${utils.escapeSystemdPath config.${options.keyfile}}"
                 '';
-                description = ''
+                description = lib.mdDoc ''
                   The cache allows tarsnap to identify previously stored data
                   blocks, reducing archival time and bandwidth usage.
 
                   Should the cache become desynchronized or corrupted, tarsnap
                   will refuse to run until you manually rebuild the cache with
-                  <command>tarsnap --fsck</command>.
+                  {command}`tarsnap --fsck`.
 
-                  Set to <literal>null</literal> to disable caching.
+                  Set to `null` to disable caching.
                 '';
               };
 
               nodump = mkOption {
                 type = types.bool;
                 default = true;
-                description = ''
-                  Exclude files with the <literal>nodump</literal> flag.
+                description = lib.mdDoc ''
+                  Exclude files with the `nodump` flag.
                 '';
               };
 
               printStats = mkOption {
                 type = types.bool;
                 default = true;
-                description = ''
+                description = lib.mdDoc ''
                   Print global archive statistics upon completion.
                   The output is available via
-                  <command>systemctl status tarsnap-archive-name</command>.
+                  {command}`systemctl status tarsnap-archive-name`.
                 '';
               };
 
               checkpointBytes = mkOption {
                 type = types.nullOr types.str;
                 default = "1GB";
-                description = ''
-                  Create a checkpoint every <literal>checkpointBytes</literal>
+                description = lib.mdDoc ''
+                  Create a checkpoint every `checkpointBytes`
                   of uploaded data (optionally specified using an SI prefix).
 
                   1GB is the minimum value. A higher value is recommended,
                   as checkpointing is expensive.
 
-                  Set to <literal>null</literal> to disable checkpointing.
+                  Set to `null` to disable checkpointing.
                 '';
               };
 
@@ -140,19 +140,18 @@ in
                 type = types.str;
                 default = "01:15";
                 example = "hourly";
-                description = ''
+                description = lib.mdDoc ''
                   Create archive at this interval.
 
                   The format is described in
-                  <citerefentry><refentrytitle>systemd.time</refentrytitle>
-                  <manvolnum>7</manvolnum></citerefentry>.
+                  {manpage}`systemd.time(7)`.
                 '';
               };
 
               aggressiveNetworking = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Upload data over multiple TCP connections, potentially
                   increasing tarsnap's bandwidth utilisation at the cost
                   of slowing down all other network traffic. Not
@@ -164,13 +163,13 @@ in
               directories = mkOption {
                 type = types.listOf types.path;
                 default = [];
-                description = "List of filesystem paths to archive.";
+                description = lib.mdDoc "List of filesystem paths to archive.";
               };
 
               excludes = mkOption {
                 type = types.listOf types.str;
                 default = [];
-                description = ''
+                description = lib.mdDoc ''
                   Exclude files and directories matching these patterns.
                 '';
               };
@@ -178,7 +177,7 @@ in
               includes = mkOption {
                 type = types.listOf types.str;
                 default = [];
-                description = ''
+                description = lib.mdDoc ''
                   Include only files and directories matching these
                   patterns (the empty list includes everything).
 
@@ -189,7 +188,7 @@ in
               lowmem = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Reduce memory consumption by not caching small files.
                   Possibly beneficial if the average file size is smaller
                   than 1 MB and the number of files is lower than the
@@ -200,9 +199,9 @@ in
               verylowmem = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Reduce memory consumption by a factor of 2 beyond what
-                  <literal>lowmem</literal> does, at the cost of significantly
+                  `lowmem` does, at the cost of significantly
                   slowing down the archiving process.
                 '';
               };
@@ -210,7 +209,7 @@ in
               maxbw = mkOption {
                 type = types.nullOr types.int;
                 default = null;
-                description = ''
+                description = lib.mdDoc ''
                   Abort archival if upstream bandwidth usage in bytes
                   exceeds this threshold.
                 '';
@@ -220,7 +219,7 @@ in
                 type = types.nullOr types.int;
                 default = null;
                 example = literalExpression "25 * 1000";
-                description = ''
+                description = lib.mdDoc ''
                   Upload bandwidth rate limit in bytes.
                 '';
               };
@@ -229,7 +228,7 @@ in
                 type = types.nullOr types.int;
                 default = null;
                 example = literalExpression "50 * 1000";
-                description = ''
+                description = lib.mdDoc ''
                   Download bandwidth rate limit in bytes.
                 '';
               };
@@ -237,21 +236,21 @@ in
               verbose = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Whether to produce verbose logging output.
                 '';
               };
               explicitSymlinks = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Whether to follow symlinks specified as archives.
                 '';
               };
               followSymlinks = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Whether to follow all symlinks in archive trees.
                 '';
               };
@@ -274,17 +273,17 @@ in
           }
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Tarsnap archive configurations. Each attribute names an archive
           to be created at a given time interval, according to the options
           associated with it. When uploading to the tarsnap server,
           archive names are suffixed by a 1 second resolution timestamp,
-          with the format <literal>%Y%m%d%H%M%S</literal>.
+          with the format `%Y%m%d%H%M%S`.
 
           For each member of the set is created a timer which triggers the
-          instanced <literal>tarsnap-archive-name</literal> service unit. You may use
-          <command>systemctl start tarsnap-archive-name</command> to
-          manually trigger creation of <literal>archive-name</literal> at
+          instanced `tarsnap-archive-name` service unit. You may use
+          {command}`systemctl start tarsnap-archive-name` to
+          manually trigger creation of `archive-name` at
           any time.
         '';
       };
diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix
index 4e690ac6ecda..c4de0b16d47d 100644
--- a/nixos/modules/services/backup/tsm.nix
+++ b/nixos/modules/services/backup/tsm.nix
@@ -8,48 +8,48 @@ 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";
       example = "incr";
-      description = ''
+      description = lib.mdDoc ''
         The actual command passed to the
-        <literal>dsmc</literal> executable to start the backup.
+        `dsmc` executable to start the backup.
       '';
     };
     servername = mkOption {
       type = nonEmptyStr;
       example = "mainTsmServer";
-      description = ''
+      description = lib.mdDoc ''
         Create a systemd system service
-        <literal>tsm-backup.service</literal> that starts
+        `tsm-backup.service` that starts
         a backup based on the given servername's stanza.
         Note that this server's
-        <option>passwdDir</option> will default to
-        <filename>/var/lib/tsm-backup/password</filename>
+        {option}`passwdDir` will default to
+        {file}`/var/lib/tsm-backup/password`
         (but may be overridden);
         also, the service will use
-        <filename>/var/lib/tsm-backup</filename> as
-        <literal>HOME</literal> when calling
-        <literal>dsmc</literal>.
+        {file}`/var/lib/tsm-backup` as
+        `HOME` when calling
+        `dsmc`.
       '';
     };
     autoTime = mkOption {
       type = nullOr nonEmptyStr;
       default = null;
       example = "12:00";
-      description = ''
+      description = lib.mdDoc ''
         The backup service will be invoked
         automatically at the given date/time,
         which must be in the format described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
-        The default <literal>null</literal>
+        {manpage}`systemd.time(5)`.
+        The default `null`
         disables automatic backups.
       '';
     };
diff --git a/nixos/modules/services/backup/zfs-replication.nix b/nixos/modules/services/backup/zfs-replication.nix
index 6d75774c78f9..ce914003c622 100644
--- a/nixos/modules/services/backup/zfs-replication.nix
+++ b/nixos/modules/services/backup/zfs-replication.nix
@@ -9,46 +9,46 @@ let
 in {
   options = {
     services.zfs.autoReplication = {
-      enable = mkEnableOption "ZFS snapshot replication.";
+      enable = mkEnableOption (lib.mdDoc "ZFS snapshot replication.");
 
       followDelete = mkOption {
-        description = "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;
       };
 
       host = mkOption {
-        description = "Remote host where snapshots should be sent. <literal>lz4</literal> is expected to be installed on this host.";
+        description = lib.mdDoc "Remote host where snapshots should be sent. `lz4` is expected to be installed on this host.";
         example = "example.com";
         type = types.str;
       };
 
       identityFilePath = mkOption {
-        description = "Path to SSH key used to login to host.";
+        description = lib.mdDoc "Path to SSH key used to login to host.";
         example = "/home/username/.ssh/id_rsa";
         type = types.path;
       };
 
       localFilesystem = mkOption {
-        description = "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;
       };
 
       remoteFilesystem = mkOption {
-        description = "Remote ZFS filesystem where snapshots should be sent.";
+        description = lib.mdDoc "Remote ZFS filesystem where snapshots should be sent.";
         example = "pool/file/path";
         type = types.str;
       };
 
       recursive = mkOption {
-        description = "Recursively discover snapshots to send.";
+        description = lib.mdDoc "Recursively discover snapshots to send.";
         default = true;
         type = types.bool;
       };
 
       username = mkOption {
-        description = "Username used by SSH to login to remote host.";
+        description = lib.mdDoc "Username used by SSH to login to remote host.";
         example = "username";
         type = types.str;
       };
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index 09e60177c390..76f147c18aff 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/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";
 
@@ -52,26 +52,26 @@ let
 
       label = mkOption {
         type = str;
-        description = "Label for this destination. Defaults to the attribute name.";
+        description = lib.mdDoc "Label for this destination. Defaults to the attribute name.";
       };
 
       plan = mkOption {
         type = str;
-        description = planDescription;
+        description = lib.mdDoc planDescription;
         example = planExample;
       };
 
       dataset = mkOption {
         type = str;
-        description = "Dataset name to send snapshots to.";
+        description = lib.mdDoc "Dataset name to send snapshots to.";
         example = "tank/main";
       };
 
       host = mkOption {
         type = nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           Host to use for the destination dataset. Can be prefixed with
-          <literal>user@</literal> to specify the ssh user.
+          `user@` to specify the ssh user.
         '';
         default = null;
         example = "john@example.com";
@@ -79,11 +79,11 @@ let
 
       presend = mkOption {
         type = nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           Command to run before sending the snapshot to the destination.
-          Intended to run a remote script via <command>ssh</command> on the
+          Intended to run a remote script via {command}`ssh` on the
           destination, e.g. to bring up a backup disk or server or to put a
-          zpool online/offline. See also <option>postsend</option>.
+          zpool online/offline. See also {option}`postsend`.
         '';
         default = null;
         example = "ssh root@bserv zpool import -Nf tank";
@@ -91,11 +91,11 @@ let
 
       postsend = mkOption {
         type = nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           Command to run after sending the snapshot to the destination.
-          Intended to run a remote script via <command>ssh</command> on the
+          Intended to run a remote script via {command}`ssh` on the
           destination, e.g. to bring up a backup disk or server or to put a
-          zpool online/offline. See also <option>presend</option>.
+          zpool online/offline. See also {option}`presend`.
         '';
         default = null;
         example = "ssh root@bserv zpool export tank";
@@ -115,32 +115,32 @@ let
 
       enable = mkOption {
         type = bool;
-        description = "Whether to enable this source.";
+        description = lib.mdDoc "Whether to enable this source.";
         default = true;
       };
 
       recursive = mkOption {
         type = bool;
-        description = "Whether to do recursive snapshots.";
+        description = lib.mdDoc "Whether to do recursive snapshots.";
         default = false;
       };
 
       mbuffer = {
         enable = mkOption {
           type = bool;
-          description = "Whether to use <command>mbuffer</command>.";
+          description = lib.mdDoc "Whether to use {command}`mbuffer`.";
           default = false;
         };
 
         port = mkOption {
           type = nullOr ints.u16;
-          description = ''
-              Port to use for <command>mbuffer</command>.
+          description = lib.mdDoc ''
+              Port to use for {command}`mbuffer`.
 
-              If this is null, it will run <command>mbuffer</command> through
+              If this is null, it will run {command}`mbuffer` through
               ssh.
 
-              If this is not null, it will run <command>mbuffer</command>
+              If this is not null, it will run {command}`mbuffer`
               directly through TCP, which is not encrypted but faster. In that
               case the given port needs to be open on the destination host.
           '';
@@ -149,8 +149,8 @@ let
 
         size = mkOption {
           type = mbufferSizeType;
-          description = ''
-            The size for <command>mbuffer</command>.
+          description = lib.mdDoc ''
+            The size for {command}`mbuffer`.
             Supports the units b, k, M, G.
           '';
           default = "1G";
@@ -160,10 +160,10 @@ let
 
       presnap = mkOption {
         type = nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           Command to run before snapshots are taken on the source dataset,
           e.g. for database locking/flushing. See also
-          <option>postsnap</option>.
+          {option}`postsnap`.
         '';
         default = null;
         example = literalExpression ''
@@ -173,9 +173,9 @@ let
 
       postsnap = mkOption {
         type = nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           Command to run after snapshots are taken on the source dataset,
-          e.g. for database unlocking. See also <option>presnap</option>.
+          e.g. for database unlocking. See also {option}`presnap`.
         '';
         default = null;
         example = literalExpression ''
@@ -185,13 +185,13 @@ let
 
       timestampFormat = mkOption {
         type = timestampType;
-        description = ''
+        description = lib.mdDoc ''
           The timestamp format to use for constructing snapshot names.
-          The syntax is <literal>strftime</literal>-like. The string must
-          consist of the mandatory <literal>%Y %m %d %H %M %S</literal>.
-          Optionally  <literal>- _ . :</literal>  characters as well as any
+          The syntax is `strftime`-like. The string must
+          consist of the mandatory `%Y %m %d %H %M %S`.
+          Optionally  `- _ . :`  characters as well as any
           alphanumeric character are allowed. If suffixed by a
-          <literal>Z</literal>, times will be in UTC.
+          `Z`, times will be in UTC.
         '';
         default = "%Y-%m-%d-%H%M%S";
         example = "znapzend-%m.%d.%Y-%H%M%SZ";
@@ -199,7 +199,7 @@ let
 
       sendDelay = mkOption {
         type = int;
-        description = ''
+        description = lib.mdDoc ''
           Specify delay (in seconds) before sending snaps to the destination.
           May be useful if you want to control sending time.
         '';
@@ -209,19 +209,19 @@ let
 
       plan = mkOption {
         type = str;
-        description = planDescription;
+        description = lib.mdDoc planDescription;
         example = planExample;
       };
 
       dataset = mkOption {
         type = str;
-        description = "The dataset to use for this source.";
+        description = lib.mdDoc "The dataset to use for this source.";
         example = "tank/home";
       };
 
       destinations = mkOption {
         type = attrsOf (destType config);
-        description = "Additional destinations.";
+        description = lib.mdDoc "Additional destinations.";
         default = {};
         example = literalExpression ''
           {
@@ -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,13 +294,13 @@ in
 {
   options = {
     services.znapzend = {
-      enable = mkEnableOption "ZnapZend ZFS backup daemon";
+      enable = mkEnableOption (lib.mdDoc "ZnapZend ZFS backup daemon");
 
       logLevel = mkOption {
         default = "debug";
         example = "warning";
         type = enum ["debug" "info" "warning" "err" "alert"];
-        description = ''
+        description = lib.mdDoc ''
           The log level when logging to file. Any of debug, info, warning, err,
           alert. Default in daemonized form is debug.
         '';
@@ -310,26 +310,26 @@ 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\>).
         '';
       };
 
       noDestroy = mkOption {
         type = bool;
         default = false;
-        description = "Does all changes to the filesystem except destroy.";
+        description = lib.mdDoc "Does all changes to the filesystem except destroy.";
       };
 
       autoCreation = mkOption {
         type = bool;
         default = false;
-        description = "Automatically create the destination dataset if it does not exist.";
+        description = lib.mdDoc "Automatically create the destination dataset if it does not exist.";
       };
 
       zetup = mkOption {
         type = attrsOf srcType;
-        description = "Znapzend configuration.";
+        description = lib.mdDoc "Znapzend configuration.";
         default = {};
         example = literalExpression ''
           {
@@ -350,7 +350,7 @@ in
 
       pure = mkOption {
         type = bool;
-        description = ''
+        description = lib.mdDoc ''
           Do not persist any stateful znapzend setups. If this option is
           enabled, your previously set znapzend setups will be cleared and only
           the ones defined with this module will be applied.
@@ -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/nixos/modules/services/backup/zrepl.nix b/nixos/modules/services/backup/zrepl.nix
index 73f5e4d9f6d7..1d3afa3eda05 100644
--- a/nixos/modules/services/backup/zrepl.nix
+++ b/nixos/modules/services/backup/zrepl.nix
@@ -11,13 +11,19 @@ in
 
   options = {
     services.zrepl = {
-      enable = mkEnableOption "zrepl";
+      enable = mkEnableOption (lib.mdDoc "zrepl");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.zrepl;
+        defaultText = literalExpression "pkgs.zrepl";
+        description = lib.mdDoc "Which package to use for zrepl";
+      };
 
       settings = mkOption {
         default = { };
-        description = ''
-          Configuration for zrepl. See <link
-          xlink:href="https://zrepl.github.io/configuration.html"/>
+        description = lib.mdDoc ''
+          Configuration for zrepl. See <https://zrepl.github.io/configuration.html>
           for more information.
         '';
         type = types.submodule {
@@ -30,14 +36,14 @@ in
   ### Implementation ###
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.zrepl ];
+    environment.systemPackages = [ cfg.package ];
 
     # zrepl looks for its config in this location by default. This
     # allows the use of e.g. `zrepl signal wakeup <job>` without having
     # to specify the storepath of the config.
     environment.etc."zrepl/zrepl.yml".source = configFile;
 
-    systemd.packages = [ pkgs.zrepl ];
+    systemd.packages = [ cfg.package ];
 
     # Note that pkgs.zrepl copies and adapts the upstream systemd unit, and
     # the fields defined here only override certain fields from that unit.
diff --git a/nixos/modules/services/blockchain/ethereum/erigon.nix b/nixos/modules/services/blockchain/ethereum/erigon.nix
new file mode 100644
index 000000000000..8ebe0fcaff54
--- /dev/null
+++ b/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/nixos/modules/services/blockchain/ethereum/geth.nix b/nixos/modules/services/blockchain/ethereum/geth.nix
index bf2cf1edd4d8..eca308dc366d 100644
--- a/nixos/modules/services/blockchain/ethereum/geth.nix
+++ b/nixos/modules/services/blockchain/ethereum/geth.nix
@@ -9,100 +9,129 @@ let
 
     options = {
 
-      enable = lib.mkEnableOption "Go Ethereum Node";
+      enable = lib.mkEnableOption (lib.mdDoc "Go Ethereum Node");
 
       port = mkOption {
         type = types.port;
         default = 30303;
-        description = "Port number Go Ethereum will be listening on, both TCP and UDP.";
+        description = lib.mdDoc "Port number Go Ethereum will be listening on, both TCP and UDP.";
       };
 
       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";
-          description = "Listen address of Go Ethereum HTTP API.";
+          description = lib.mdDoc "Listen address of Go Ethereum HTTP API.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8545;
-          description = "Port number of Go Ethereum HTTP API.";
+          description = lib.mdDoc "Port number of Go Ethereum HTTP API.";
         };
 
         apis = mkOption {
           type = types.nullOr (types.listOf types.str);
           default = null;
-          description = "APIs to enable over WebSocket";
+          description = lib.mdDoc "APIs to enable over WebSocket";
           example = ["net" "eth"];
         };
       };
 
       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";
-          description = "Listen address of Go Ethereum WebSocket API.";
+          description = lib.mdDoc "Listen address of Go Ethereum WebSocket API.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8546;
-          description = "Port number of Go Ethereum WebSocket API.";
+          description = lib.mdDoc "Port number of Go Ethereum WebSocket API.";
         };
 
         apis = mkOption {
           type = types.nullOr (types.listOf types.str);
           default = null;
-          description = "APIs to enable over WebSocket";
+          description = lib.mdDoc "APIs to enable over WebSocket";
           example = ["net" "eth"];
         };
       };
 
+      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";
-          description = "Listen address of Go Ethereum metrics service.";
+          description = lib.mdDoc "Listen address of Go Ethereum metrics service.";
         };
 
         port = mkOption {
           type = types.port;
           default = 6060;
-          description = "Port number of Go Ethereum metrics service.";
+          description = lib.mdDoc "Port number of Go Ethereum metrics service.";
         };
       };
 
       network = mkOption {
         type = types.nullOr (types.enum [ "goerli" "rinkeby" "yolov2" "ropsten" ]);
         default = null;
-        description = "The network to connect to. Mainnet (null) is the default ethereum network.";
+        description = lib.mdDoc "The network to connect to. Mainnet (null) is the default ethereum network.";
       };
 
       syncmode = mkOption {
         type = types.enum [ "snap" "fast" "full" "light" ];
         default = "snap";
-        description = "Blockchain sync mode.";
+        description = lib.mdDoc "Blockchain sync mode.";
       };
 
       gcmode = mkOption {
         type = types.enum [ "full" "archive" ];
         default = "full";
-        description = "Blockchain garbage collection mode.";
+        description = lib.mdDoc "Blockchain garbage collection mode.";
       };
 
       maxpeers = mkOption {
         type = types.int;
         default = 50;
-        description = "Maximum peers to connect to.";
+        description = lib.mdDoc "Maximum peers to connect to.";
       };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
-        description = "Additional arguments passed to Go Ethereum.";
+        description = lib.mdDoc "Additional arguments passed to Go Ethereum.";
         default = [];
       };
 
@@ -110,7 +139,7 @@ let
         default = pkgs.go-ethereum.geth;
         defaultText = literalExpression "pkgs.go-ethereum.geth";
         type = types.package;
-        description = "Package to use as Go Ethereum node.";
+        description = lib.mdDoc "Package to use as Go Ethereum node.";
       };
     };
   };
@@ -124,7 +153,7 @@ in
     services.geth = mkOption {
       type = types.attrsOf (types.submodule gethOpts);
       default = {};
-      description = "Specification of one or more geth instances.";
+      description = lib.mdDoc "Specification of one or more geth instances.";
     };
   };
 
@@ -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";
@@ -169,8 +201,10 @@ in
           ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \
           ${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/nixos/modules/services/blockchain/ethereum/lighthouse.nix b/nixos/modules/services/blockchain/ethereum/lighthouse.nix
new file mode 100644
index 000000000000..863e737d908a
--- /dev/null
+++ b/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/nixos/modules/services/cluster/corosync/default.nix b/nixos/modules/services/cluster/corosync/default.nix
index b4144917feea..7ef17c46b81e 100644
--- a/nixos/modules/services/cluster/corosync/default.nix
+++ b/nixos/modules/services/cluster/corosync/default.nix
@@ -7,43 +7,43 @@ in
 {
   # interface
   options.services.corosync = {
-    enable = mkEnableOption "corosync";
+    enable = mkEnableOption (lib.mdDoc "corosync");
 
     package = mkOption {
       type = types.package;
       default = pkgs.corosync;
       defaultText = literalExpression "pkgs.corosync";
-      description = "Package that should be used for corosync.";
+      description = lib.mdDoc "Package that should be used for corosync.";
     };
 
     clusterName = mkOption {
       type = types.str;
       default = "nixcluster";
-      description = "Name of the corosync cluster.";
+      description = lib.mdDoc "Name of the corosync cluster.";
     };
 
     extraOptions = mkOption {
       type = with types; listOf str;
       default = [];
-      description = "Additional options with which to start corosync.";
+      description = lib.mdDoc "Additional options with which to start corosync.";
     };
 
     nodelist = mkOption {
-      description = "Corosync nodelist: all cluster members.";
+      description = lib.mdDoc "Corosync nodelist: all cluster members.";
       default = [];
       type = with types; listOf (submodule {
         options = {
           nodeid = mkOption {
             type = int;
-            description = "Node ID number";
+            description = lib.mdDoc "Node ID number";
           };
           name = mkOption {
             type = str;
-            description = "Node name";
+            description = lib.mdDoc "Node name";
           };
           ring_addrs = mkOption {
             type = listOf str;
-            description = "List of addresses, one for each ring.";
+            description = lib.mdDoc "List of addresses, one for each ring.";
           };
         };
       });
diff --git a/nixos/modules/services/cluster/hadoop/conf.nix b/nixos/modules/services/cluster/hadoop/conf.nix
index e3c26a0d5505..388eaafcc362 100644
--- a/nixos/modules/services/cluster/hadoop/conf.nix
+++ b/nixos/modules/services/cluster/hadoop/conf.nix
@@ -33,6 +33,7 @@ pkgs.runCommand "hadoop-conf" {} (with cfg; ''
   mkdir -p $out/
   cp ${siteXml "core-site.xml" (coreSite // coreSiteInternal)}/* $out/
   cp ${siteXml "hdfs-site.xml" (hdfsSiteDefault // hdfsSite // hdfsSiteInternal)}/* $out/
+  cp ${siteXml "hbase-site.xml" (hbaseSiteDefault // hbaseSite // hbaseSiteInternal)}/* $out/
   cp ${siteXml "mapred-site.xml" (mapredSiteDefault // mapredSite)}/* $out/
   cp ${siteXml "yarn-site.xml" (yarnSiteDefault // yarnSite // yarnSiteInternal)}/* $out/
   cp ${siteXml "httpfs-site.xml" httpfsSite}/* $out/
@@ -40,5 +41,5 @@ pkgs.runCommand "hadoop-conf" {} (with cfg; ''
   cp ${pkgs.writeTextDir "hadoop-user-functions.sh" userFunctions}/* $out/
   cp ${pkgs.writeTextDir "hadoop-env.sh" hadoopEnv}/* $out/
   cp ${log4jProperties} $out/log4j.properties
-  ${lib.concatMapStringsSep "\n" (dir: "cp -r ${dir}/* $out/") extraConfDirs}
+  ${lib.concatMapStringsSep "\n" (dir: "cp -f -r ${dir}/* $out/") extraConfDirs}
 '')
diff --git a/nixos/modules/services/cluster/hadoop/default.nix b/nixos/modules/services/cluster/hadoop/default.nix
index a4fdea81037c..72bf25c21146 100644
--- a/nixos/modules/services/cluster/hadoop/default.nix
+++ b/nixos/modules/services/cluster/hadoop/default.nix
@@ -5,7 +5,7 @@ let
 in
 with lib;
 {
-  imports = [ ./yarn.nix ./hdfs.nix ];
+  imports = [ ./yarn.nix ./hdfs.nix ./hbase.nix ];
 
   options.services.hadoop = {
     coreSite = mkOption {
@@ -16,16 +16,16 @@ with lib;
           "fs.defaultFS" = "hdfs://localhost";
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Hadoop core-site.xml definition
-        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/core-default.xml"/>
+        <https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/core-default.xml>
       '';
     };
     coreSiteInternal = mkOption {
       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
       '';
     };
@@ -38,7 +38,7 @@ with lib;
         "dfs.namenode.http-bind-host" = "0.0.0.0";
       };
       type = types.attrsOf types.anything;
-      description = ''
+      description = lib.mdDoc ''
         Default options for hdfs-site.xml
       '';
     };
@@ -50,16 +50,16 @@ with lib;
           "dfs.nameservices" = "namenode1";
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Additional options and overrides for hdfs-site.xml
-        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml"/>
+        <https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml>
       '';
     };
     hdfsSiteInternal = mkOption {
       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
       '';
     };
@@ -80,7 +80,7 @@ with lib;
         }
       '';
       type = types.attrsOf types.anything;
-      description = ''
+      description = lib.mdDoc ''
         Default options for mapred-site.xml
       '';
     };
@@ -92,9 +92,9 @@ with lib;
           "mapreduce.map.java.opts" = "-Xmx900m -XX:+UseParallelGC";
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Additional options and overrides for mapred-site.xml
-        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/mapred-default.xml"/>
+        <https://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/mapred-default.xml>
       '';
     };
 
@@ -113,7 +113,7 @@ with lib;
         "yarn.resourcemanager.scheduler.class" = "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler";
       };
       type = types.attrsOf types.anything;
-      description = ''
+      description = lib.mdDoc ''
         Default options for yarn-site.xml
       '';
     };
@@ -125,16 +125,16 @@ with lib;
           "yarn.resourcemanager.hostname" = "''${config.networking.hostName}";
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Additional options and overrides for yarn-site.xml
-        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-common/yarn-default.xml"/>
+        <https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-common/yarn-default.xml>
       '';
     };
     yarnSiteInternal = mkOption {
       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
       '';
     };
@@ -147,9 +147,9 @@ with lib;
           "hadoop.http.max.threads" = 500;
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Hadoop httpfs-site.xml definition
-        <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-hdfs-httpfs/httpfs-default.html"/>
+        <https://hadoop.apache.org/docs/current/hadoop-hdfs-httpfs/httpfs-default.html>
       '';
     };
 
@@ -162,7 +162,7 @@ with lib;
       example = literalExpression ''
         "''${pkgs.hadoop}/lib/''${pkgs.hadoop.untarDir}/etc/hadoop/log4j.properties";
       '';
-      description = "log4j.properties file added to HADOOP_CONF_DIR";
+      description = lib.mdDoc "log4j.properties file added to HADOOP_CONF_DIR";
     };
 
     containerExecutorCfg = mkOption {
@@ -179,9 +179,9 @@ with lib;
           "feature.terminal.enabled" = 0;
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Yarn container-executor.cfg definition
-        <link xlink:href="https://hadoop.apache.org/docs/r2.7.2/hadoop-yarn/hadoop-yarn-site/SecureContainer.html"/>
+        <https://hadoop.apache.org/docs/r2.7.2/hadoop-yarn/hadoop-yarn-site/SecureContainer.html>
       '';
     };
 
@@ -194,16 +194,16 @@ with lib;
           ./extraYARNConfs
         ]
       '';
-      description = "Directories containing additional config files to be added to HADOOP_CONF_DIR";
+      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/nixos/modules/services/cluster/hadoop/hbase.nix b/nixos/modules/services/cluster/hadoop/hbase.nix
new file mode 100644
index 000000000000..97951ebfe334
--- /dev/null
+++ b/nixos/modules/services/cluster/hadoop/hbase.nix
@@ -0,0 +1,196 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+let
+  cfg = config.services.hadoop;
+  hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
+  mkIfNotNull = x: mkIf (x != null) x;
+in
+{
+  options.services.hadoop = {
+
+    gatewayRole.enableHbaseCli = mkEnableOption (lib.mdDoc "HBase CLI tools");
+
+    hbaseSiteDefault = mkOption {
+      default = {
+        "hbase.regionserver.ipc.address" = "0.0.0.0";
+        "hbase.master.ipc.address" = "0.0.0.0";
+        "hbase.master.info.bindAddress" = "0.0.0.0";
+        "hbase.regionserver.info.bindAddress" = "0.0.0.0";
+
+        "hbase.cluster.distributed" = "true";
+      };
+      type = types.attrsOf types.anything;
+      description = lib.mdDoc ''
+        Default options for hbase-site.xml
+      '';
+    };
+    hbaseSite = mkOption {
+      default = {};
+      type = with types; attrsOf anything;
+      example = literalExpression ''
+      '';
+      description = lib.mdDoc ''
+        Additional options and overrides for hbase-site.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 = lib.mdDoc ''
+        Internal option to add configs to hbase-site.xml based on module options
+      '';
+    };
+
+    hbase = {
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.hbase;
+        defaultText = literalExpression "pkgs.hbase";
+        description = lib.mdDoc "HBase package";
+      };
+
+      rootdir = mkOption {
+        description = lib.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.
+          If a core-site.xml is provided, the FS scheme defaults to the value
+          of "fs.defaultFS".
+
+          Filesystems other than HDFS (like S3, QFS, Swift) are also supported.
+        '';
+        type = types.str;
+        example = "hdfs://nameservice1/hbase";
+        default = "/hbase";
+      };
+      zookeeperQuorum = mkOption {
+        description = lib.mdDoc ''
+          This option will set "hbase.zookeeper.quorum" in hbase-site.xml.
+          Comma separated list of servers in the ZooKeeper ensemble.
+        '';
+        type = with types; nullOr commas;
+        example = "zk1.internal,zk2.internal,zk3.internal";
+        default = null;
+      };
+      master = {
+        enable = mkEnableOption (lib.mdDoc "HBase Master");
+        initHDFS = mkEnableOption (lib.mdDoc "initialization of the hbase directory on HDFS");
+
+        openFirewall = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Open firewall ports for HBase master.
+          '';
+        };
+      };
+      regionServer = {
+        enable = mkEnableOption (lib.mdDoc "HBase RegionServer");
+
+        overrideHosts = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.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.
+
+          '';
+        };
+
+        openFirewall = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Open firewall ports for HBase master.
+          '';
+        };
+      };
+    };
+  };
+
+  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}
+        '';
+
+        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";
+        };
+      };
+
+      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 [ ];
+        };
+      };
+    })
+
+    (mkIf cfg.gatewayRole.enable {
+
+      environment.systemPackages = mkIf cfg.gatewayRole.enableHbaseCli [ cfg.hbase.package ];
+
+      services.hadoop.hbaseSiteInternal = with cfg.hbase; {
+        "hbase.zookeeper.quorum" = mkIfNotNull zookeeperQuorum;
+      };
+
+      users.users.hbase = {
+        description = "Hadoop HBase user";
+        group = "hadoop";
+        isSystemUser = true;
+      };
+    })
+  ];
+}
diff --git a/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixos/modules/services/cluster/hadoop/hdfs.nix
index 325a002ad32f..4a49bd0ddd43 100644
--- a/nixos/modules/services/cluster/hadoop/hdfs.nix
+++ b/nixos/modules/services/cluster/hadoop/hdfs.nix
@@ -8,10 +8,10 @@ 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 = ''
+      description = lib.mdDoc ''
         Automatically restart the service on config change.
         This can be set to false to defer restarts on clusters running critical applications.
         Please consider the security implications of inadvertently running an older version,
@@ -22,7 +22,7 @@ let
     extraFlags = mkOption{
       type = with types; listOf str;
       default = [];
-      description = "Extra command line flags to pass to ${serviceName}";
+      description = lib.mdDoc "Extra command line flags to pass to ${serviceName}";
       example = [
         "-Dcom.sun.management.jmxremote"
         "-Dcom.sun.management.jmxremote.port=8010"
@@ -31,13 +31,13 @@ let
     extraEnv = mkOption{
       type = with types; attrsOf str;
       default = {};
-      description = "Extra environment variables for ${serviceName}";
+      description = lib.mdDoc "Extra environment variables for ${serviceName}";
     };
   } // (optionalAttrs firewallOption {
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = "Open firewall ports for ${serviceName}.";
+      description = lib.mdDoc "Open firewall ports for ${serviceName}.";
     };
   }) // (optionalAttrs (extraOpts != null) extraOpts);
 
@@ -83,12 +83,12 @@ in
       formatOnInit = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Format HDFS namenode on first start. This is useful for quickly spinning up
           ephemeral HDFS clusters with a single namenode.
           For HA clusters, initialization involves multiple steps across multiple nodes.
           Follow this guide to initialize an HA cluster manually:
-          <link xlink:href="https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html"/>
+          <https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html>
         '';
       };
     };
@@ -96,19 +96,19 @@ in
     datanode = hadoopServiceOption { serviceName = "HDFS DataNode"; } // {
       dataDirs = mkOption {
         default = null;
-        description = "Tier and path definitions for datanode storage.";
+        description = lib.mdDoc "Tier and path definitions for datanode storage.";
         type = with types; nullOr (listOf (submodule {
           options = {
             type = mkOption {
               type = enum [ "SSD" "DISK" "ARCHIVE" "RAM_DISK" ];
-              description = ''
+              description = lib.mdDoc ''
                 Storage types ([SSD]/[DISK]/[ARCHIVE]/[RAM_DISK]) for HDFS storage policies.
               '';
             };
             path = mkOption {
               type = path;
               example = [ "/var/lib/hadoop/hdfs/dn" ];
-              description = "Determines where on the local filesystem a data node should store its blocks.";
+              description = lib.mdDoc "Determines where on the local filesystem a data node should store its blocks.";
             };
           };
         }));
@@ -126,7 +126,7 @@ in
       tempPath = mkOption {
         type = types.path;
         default = "/tmp/hadoop/httpfs";
-        description = "HTTPFS_TEMP path used by HTTPFS";
+        description = lib.mdDoc "HTTPFS_TEMP path used by HTTPFS";
       };
     };
 
@@ -158,8 +158,8 @@ in
         50010 # datanode.address
         50020 # datanode.ipc.address
       ];
-      extraConfig.services.hadoop.hdfsSiteInternal."dfs.datanode.data.dir" = let d = cfg.hdfs.datanode.dataDirs; in
-        if (d!= null) then (concatMapStringsSep "," (x: "["+x.type+"]file://"+x.path) cfg.hdfs.datanode.dataDirs) else d;
+      extraConfig.services.hadoop.hdfsSiteInternal."dfs.datanode.data.dir" = mkIf (cfg.hdfs.datanode.dataDirs!= null)
+        (concatMapStringsSep "," (x: "["+x.type+"]file://"+x.path) cfg.hdfs.datanode.dataDirs);
     })
 
     (hadoopServiceConfig {
diff --git a/nixos/modules/services/cluster/hadoop/yarn.nix b/nixos/modules/services/cluster/hadoop/yarn.nix
index 74e16bdec687..26077f35fdd0 100644
--- a/nixos/modules/services/cluster/hadoop/yarn.nix
+++ b/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -5,7 +5,7 @@ let
   hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/";
   restartIfChanged  = mkOption {
     type = types.bool;
-    description = ''
+    description = lib.mdDoc ''
       Automatically restart the service on config change.
       This can be set to false to defer restarts on clusters running critical applications.
       Please consider the security implications of inadvertently running an older version,
@@ -16,7 +16,7 @@ let
   extraFlags = mkOption{
     type = with types; listOf str;
     default = [];
-    description = "Extra command line flags to pass to the service";
+    description = lib.mdDoc "Extra command line flags to pass to the service";
     example = [
       "-Dcom.sun.management.jmxremote"
       "-Dcom.sun.management.jmxremote.port=8010"
@@ -25,45 +25,45 @@ let
   extraEnv = mkOption{
     type = with types; attrsOf str;
     default = {};
-    description = "Extra environment variables";
+    description = lib.mdDoc "Extra environment variables";
   };
 in
 {
   options.services.hadoop.yarn = {
     resourcemanager = {
-      enable = mkEnableOption "Hadoop YARN ResourceManager";
+      enable = mkEnableOption (lib.mdDoc "Hadoop YARN ResourceManager");
       inherit restartIfChanged extraFlags extraEnv;
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open firewall ports for resourcemanager
         '';
       };
     };
     nodemanager = {
-      enable = mkEnableOption "Hadoop YARN NodeManager";
+      enable = mkEnableOption (lib.mdDoc "Hadoop YARN NodeManager");
       inherit restartIfChanged extraFlags extraEnv;
 
       resource = {
         cpuVCores = mkOption {
-          description = "Number of vcores that can be allocated for containers.";
+          description = lib.mdDoc "Number of vcores that can be allocated for containers.";
           type = with types; nullOr ints.positive;
           default = null;
         };
         maximumAllocationVCores = mkOption {
-          description = "The maximum virtual CPU cores any container can be allocated.";
+          description = lib.mdDoc "The maximum virtual CPU cores any container can be allocated.";
           type = with types; nullOr ints.positive;
           default = null;
         };
         memoryMB = mkOption {
-          description = "Amount of physical memory, in MB, that can be allocated for containers.";
+          description = lib.mdDoc "Amount of physical memory, in MB, that can be allocated for containers.";
           type = with types; nullOr ints.positive;
           default = null;
         };
         maximumAllocationMB = mkOption {
-          description = "The maximum physical memory any container can be allocated.";
+          description = lib.mdDoc "The maximum physical memory any container can be allocated.";
           type = with types; nullOr ints.positive;
           default = null;
         };
@@ -72,13 +72,13 @@ in
       useCGroups = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Use cgroups to enforce resource limits on containers
         '';
       };
 
       localDir = mkOption {
-        description = "List of directories to store localized files in.";
+        description = lib.mdDoc "List of directories to store localized files in.";
         type = with types; nullOr (listOf path);
         example = [ "/var/lib/hadoop/yarn/nm" ];
         default = null;
@@ -87,14 +87,14 @@ in
       addBinBash = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Add /bin/bash. This is needed by the linux container executor's launch script.
         '';
       };
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open firewall ports for nodemanager.
           Because containers can listen on any ephemeral port, TCP ports 1024–65535 will be opened.
         '';
@@ -178,18 +178,18 @@ in
 
       services.hadoop.gatewayRole.enable = true;
 
-      services.hadoop.yarnSiteInternal = with cfg.yarn.nodemanager; {
-        "yarn.nodemanager.local-dirs" = localDir;
+      services.hadoop.yarnSiteInternal = with cfg.yarn.nodemanager; mkMerge [ ({
+        "yarn.nodemanager.local-dirs" = mkIf (localDir!= null) (concatStringsSep "," localDir);
         "yarn.scheduler.maximum-allocation-vcores" = resource.maximumAllocationVCores;
         "yarn.scheduler.maximum-allocation-mb" = resource.maximumAllocationMB;
         "yarn.nodemanager.resource.cpu-vcores" = resource.cpuVCores;
         "yarn.nodemanager.resource.memory-mb" = resource.memoryMB;
-      } // mkIf useCGroups {
+      }) (mkIf useCGroups {
         "yarn.nodemanager.linux-container-executor.cgroups.hierarchy" = "/hadoop-yarn";
         "yarn.nodemanager.linux-container-executor.resources-handler.class" = "org.apache.hadoop.yarn.server.nodemanager.util.CgroupsLCEResourcesHandler";
         "yarn.nodemanager.linux-container-executor.cgroups.mount" = "true";
         "yarn.nodemanager.linux-container-executor.cgroups.mount-path" = "/run/wrappers/yarn-nodemanager/cgroup";
-      };
+      })];
 
       networking.firewall.allowedTCPPortRanges = [
         (mkIf (cfg.yarn.nodemanager.openFirewall) {from = 1024; to = 65535;})
diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix
index 3a36cfa3f37b..693f388de14a 100644
--- a/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixos/modules/services/cluster/k3s/default.nix
@@ -3,23 +3,39 @@
 with lib;
 let
   cfg = config.services.k3s;
+  removeOption = config: instruction:
+    lib.mkRemovedOptionModule ([ "services" "k3s" ] ++ config) instruction;
 in
 {
+  imports = [
+    (removeOption [ "docker" ] "k3s docker option is no longer supported.")
+  ];
+
   # interface
   options.services.k3s = {
-    enable = mkEnableOption "k3s";
+    enable = mkEnableOption (lib.mdDoc "k3s");
 
     package = mkOption {
       type = types.package;
       default = pkgs.k3s;
       defaultText = literalExpression "pkgs.k3s";
-      description = "Package that should be used for k3s";
+      description = lib.mdDoc "Package that should be used for k3s";
     };
 
     role = mkOption {
-      description = ''
+      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" ];
@@ -27,15 +43,44 @@ in
 
     serverAddr = mkOption {
       type = types.str;
-      description = "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 = ''
-        The k3s token to use when connecting to the server. This option only makes sense for an agent.
+      description = lib.mdDoc ''
+        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.
       '';
@@ -44,18 +89,12 @@ in
 
     tokenFile = mkOption {
       type = types.nullOr types.path;
-      description = "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;
     };
 
-    docker = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Use docker to run containers rather than the built-in containerd.";
-    };
-
     extraFlags = mkOption {
-      description = "Extra flags to pass to the k3s command.";
+      description = lib.mdDoc "Extra flags to pass to the k3s command.";
       type = types.str;
       default = "";
       example = "--no-deploy traefik --cluster-cidr 10.24.0.0/16";
@@ -64,13 +103,13 @@ in
     disableAgent = mkOption {
       type = types.bool;
       default = false;
-      description = "Only run the server. This option only makes sense for a server.";
+      description = lib.mdDoc "Only run the server. This option only makes sense for a server.";
     };
 
     configPath = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = "File path containing the k3s YAML config. This is useful when the config is generated (for example on boot).";
+      description = lib.mdDoc "File path containing the k3s YAML config. This is useful when the config is generated (for example on boot).";
     };
   };
 
@@ -86,16 +125,21 @@ 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'";
+      }
     ];
 
-    virtualisation.docker = mkIf cfg.docker {
-      enable = mkDefault true;
-    };
     environment.systemPackages = [ config.services.k3s.package ];
 
     systemd.services.k3s = {
       description = "k3s service";
-      after = [ "network.service" "firewall.service" ] ++ (optional cfg.docker "docker.service");
+      after = [ "network.service" "firewall.service" ];
       wants = [ "network.service" "firewall.service" ];
       wantedBy = [ "multi-user.target" ];
       path = optional config.boot.zfs.enabled config.boot.zfs.package;
@@ -113,8 +157,8 @@ in
         ExecStart = concatStringsSep " \\\n " (
           [
             "${cfg.package}/bin/k3s ${cfg.role}"
-          ] ++ (optional cfg.docker "--docker")
-          ++ (optional (cfg.docker && config.systemd.enableUnifiedCgroupHierarchy) "--kubelet-arg=cgroup-driver=systemd")
+          ]
+          ++ (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/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index b677d900ff50..7aa2a8323b1d 100644
--- a/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -21,8 +21,8 @@ in
   options.services.kubernetes.addonManager = with lib.types; {
 
     bootstrapAddons = mkOption {
-      description = ''
-        Bootstrap addons are like regular addons, but they are applied with cluster-admin rigths.
+      description = lib.mdDoc ''
+        Bootstrap addons are like regular addons, but they are applied with cluster-admin rights.
         They are applied at addon-manager startup only.
       '';
       default = { };
@@ -43,7 +43,7 @@ in
     };
 
     addons = mkOption {
-      description = "Kubernetes addons (any kind of Kubernetes resource can be an addon).";
+      description = lib.mdDoc "Kubernetes addons (any kind of Kubernetes resource can be an addon).";
       default = { };
       type = attrsOf (either attrs (listOf attrs));
       example = literalExpression ''
@@ -62,7 +62,7 @@ in
       '';
     };
 
-    enable = mkEnableOption "Kubernetes addon manager.";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes addon manager.");
   };
 
   ###### implementation
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 7bd4991f43f7..3d41b5f00853 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -12,10 +12,10 @@ let
   };
 in {
   options.services.kubernetes.addons.dns = {
-    enable = mkEnableOption "kubernetes dns addon";
+    enable = mkEnableOption (lib.mdDoc "kubernetes dns addon");
 
     clusterIp = mkOption {
-      description = "Dns addon clusterIP";
+      description = lib.mdDoc "Dns addon clusterIP";
 
       # this default is also what kubernetes users
       default = (
@@ -23,39 +23,39 @@ 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;
     };
 
     clusterDomain = mkOption {
-      description = "Dns cluster domain";
+      description = lib.mdDoc "Dns cluster domain";
       default = "cluster.local";
       type = types.str;
     };
 
     replicas = mkOption {
-      description = "Number of DNS pod replicas to deploy in the cluster.";
+      description = lib.mdDoc "Number of DNS pod replicas to deploy in the cluster.";
       default = 2;
       type = types.int;
     };
 
     reconcileMode = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Controls the addon manager reconciliation mode for the DNS addon.
 
         Setting reconcile mode to EnsureExists makes it possible to tailor DNS behavior by editing the coredns ConfigMap.
 
-        See: <link xlink:href="https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/addon-manager/README.md"/>.
+        See: <https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/addon-manager/README.md>.
       '';
       default = "Reconcile";
       type = types.enum [ "Reconcile" "EnsureExists" ];
     };
 
     coredns = mkOption {
-      description = "Docker image to seed for the CoreDNS container.";
+      description = lib.mdDoc "Docker image to seed for the CoreDNS container.";
       type = types.attrs;
       default = {
         imageName = "coredns/coredns";
@@ -66,10 +66,10 @@ in {
     };
 
     corefile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Custom coredns corefile configuration.
 
-        See: <link xlink:href="https://coredns.io/manual/toc/#configuration"/>.
+        See: <https://coredns.io/manual/toc/#configuration>.
       '';
       type = types.str;
       default = ''
diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix
index a192e93badc2..d5ec1e5e6d26 100644
--- a/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/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" ])
@@ -30,7 +31,7 @@ in
   options.services.kubernetes.apiserver = with lib.types; {
 
     advertiseAddress = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver IP address on which to advertise the apiserver
         to members of the cluster. This address must be reachable by the rest
         of the cluster.
@@ -40,40 +41,40 @@ in
     };
 
     allowPrivileged = mkOption {
-      description = "Whether to allow privileged containers on Kubernetes.";
+      description = lib.mdDoc "Whether to allow privileged containers on Kubernetes.";
       default = false;
       type = bool;
     };
 
     authorizationMode = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver authorization mode (AlwaysAllow/AlwaysDeny/ABAC/Webhook/RBAC/Node). See
-        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/"/>
+        <https://kubernetes.io/docs/reference/access-authn-authz/authorization/>
       '';
       default = ["RBAC" "Node"]; # Enabling RBAC by default, although kubernetes default is AllowAllow
       type = listOf (enum ["AlwaysAllow" "AlwaysDeny" "ABAC" "Webhook" "RBAC" "Node"]);
     };
 
     authorizationPolicy = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver authorization policy file. See
-        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/"/>
+        <https://kubernetes.io/docs/reference/access-authn-authz/authorization/>
       '';
       default = [];
       type = listOf attrs;
     };
 
     basicAuthFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver basic authentication file. See
-        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authentication"/>
+        <https://kubernetes.io/docs/reference/access-authn-authz/authentication>
       '';
       default = null;
       type = nullOr path;
     };
 
     bindAddress = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The IP address on which to listen for the --secure-port port.
         The associated interface(s) must be reachable by the rest
         of the cluster, and by CLI/web clients.
@@ -83,27 +84,27 @@ in
     };
 
     clientCaFile = mkOption {
-      description = "Kubernetes apiserver CA file for client auth.";
+      description = lib.mdDoc "Kubernetes apiserver CA file for client auth.";
       default = top.caFile;
       defaultText = literalExpression "config.${otop.caFile}";
       type = nullOr path;
     };
 
     disableAdmissionPlugins = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes admission control plugins to disable. See
-        <link xlink:href="https://kubernetes.io/docs/admin/admission-controllers/"/>
+        <https://kubernetes.io/docs/admin/admission-controllers/>
       '';
       default = [];
       type = listOf str;
     };
 
-    enable = mkEnableOption "Kubernetes apiserver";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes apiserver");
 
     enableAdmissionPlugins = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes admission control plugins to enable. See
-        <link xlink:href="https://kubernetes.io/docs/admin/admission-controllers/"/>
+        <https://kubernetes.io/docs/admin/admission-controllers/>
       '';
       default = [
         "NamespaceLifecycle" "LimitRanger" "ServiceAccount"
@@ -120,25 +121,25 @@ in
 
     etcd = {
       servers = mkOption {
-        description = "List of etcd servers.";
+        description = lib.mdDoc "List of etcd servers.";
         default = ["http://127.0.0.1:2379"];
         type = types.listOf types.str;
       };
 
       keyFile = mkOption {
-        description = "Etcd key file.";
+        description = lib.mdDoc "Etcd key file.";
         default = null;
         type = types.nullOr types.path;
       };
 
       certFile = mkOption {
-        description = "Etcd cert file.";
+        description = lib.mdDoc "Etcd cert file.";
         default = null;
         type = types.nullOr types.path;
       };
 
       caFile = mkOption {
-        description = "Etcd ca file.";
+        description = lib.mdDoc "Etcd ca file.";
         default = top.caFile;
         defaultText = literalExpression "config.${otop.caFile}";
         type = types.nullOr types.path;
@@ -146,77 +147,65 @@ in
     };
 
     extraOpts = mkOption {
-      description = "Kubernetes apiserver extra command line options.";
+      description = lib.mdDoc "Kubernetes apiserver extra command line options.";
       default = "";
       type = separatedString " ";
     };
 
     extraSANs = mkOption {
-      description = "Extra x509 Subject Alternative Names to be added to the kubernetes apiserver tls cert.";
+      description = lib.mdDoc "Extra x509 Subject Alternative Names to be added to the kubernetes apiserver tls cert.";
       default = [];
       type = listOf str;
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = lib.mdDoc "List set of feature gates";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
-    insecureBindAddress = mkOption {
-      description = "The IP address on which to serve the --insecure-port.";
-      default = "127.0.0.1";
-      type = str;
-    };
-
-    insecurePort = mkOption {
-      description = "Kubernetes apiserver insecure listening port. (0 = disabled)";
-      default = 0;
-      type = int;
-    };
-
     kubeletClientCaFile = mkOption {
-      description = "Path to a cert file for connecting to kubelet.";
+      description = lib.mdDoc "Path to a cert file for connecting to kubelet.";
       default = top.caFile;
       defaultText = literalExpression "config.${otop.caFile}";
       type = nullOr path;
     };
 
     kubeletClientCertFile = mkOption {
-      description = "Client certificate to use for connections to kubelet.";
+      description = lib.mdDoc "Client certificate to use for connections to kubelet.";
       default = null;
       type = nullOr path;
     };
 
     kubeletClientKeyFile = mkOption {
-      description = "Key to use for connections to kubelet.";
+      description = lib.mdDoc "Key to use for connections to kubelet.";
       default = null;
       type = nullOr path;
     };
 
     preferredAddressTypes = mkOption {
-      description = "List of the preferred NodeAddressTypes to use for kubelet connections.";
+      description = lib.mdDoc "List of the preferred NodeAddressTypes to use for kubelet connections.";
       type = nullOr str;
       default = null;
     };
 
     proxyClientCertFile = mkOption {
-      description = "Client certificate to use for connections to proxy.";
+      description = lib.mdDoc "Client certificate to use for connections to proxy.";
       default = null;
       type = nullOr path;
     };
 
     proxyClientKeyFile = mkOption {
-      description = "Key to use for connections to proxy.";
+      description = lib.mdDoc "Key to use for connections to proxy.";
       default = null;
       type = nullOr path;
     };
 
     runtimeConfig = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Api runtime configuration. See
-        <link xlink:href="https://kubernetes.io/docs/tasks/administer-cluster/cluster-management/"/>
+        <https://kubernetes.io/docs/tasks/administer-cluster/cluster-management/>
       '';
       default = "authentication.k8s.io/v1beta1=true";
       example = "api/all=false,api/v1=true";
@@ -224,7 +213,7 @@ in
     };
 
     storageBackend = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver storage backend.
       '';
       default = "etcd3";
@@ -232,13 +221,13 @@ in
     };
 
     securePort = mkOption {
-      description = "Kubernetes apiserver secure port.";
+      description = lib.mdDoc "Kubernetes apiserver secure port.";
       default = 6443;
       type = int;
     };
 
     apiAudiences = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver ServiceAccount issuer.
       '';
       default = "api,https://kubernetes.default.svc";
@@ -246,7 +235,7 @@ in
     };
 
     serviceAccountIssuer = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver ServiceAccount issuer.
       '';
       default = "https://kubernetes.default.svc";
@@ -254,7 +243,7 @@ in
     };
 
     serviceAccountSigningKeyFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Path to the file that contains the current private key of the service
         account token issuer. The issuer will sign issued ID tokens with this
         private key.
@@ -263,7 +252,7 @@ in
     };
 
     serviceAccountKeyFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         File containing PEM-encoded x509 RSA or ECDSA private or public keys,
         used to verify ServiceAccount tokens. The specified file can contain
         multiple keys, and the flag can be specified multiple times with
@@ -274,7 +263,7 @@ in
     };
 
     serviceClusterIpRange = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         A CIDR notation IP range from which to assign service cluster IPs.
         This must not overlap with any IP ranges assigned to nodes for pods.
       '';
@@ -283,39 +272,39 @@ in
     };
 
     tlsCertFile = mkOption {
-      description = "Kubernetes apiserver certificate file.";
+      description = lib.mdDoc "Kubernetes apiserver certificate file.";
       default = null;
       type = nullOr path;
     };
 
     tlsKeyFile = mkOption {
-      description = "Kubernetes apiserver private key file.";
+      description = lib.mdDoc "Kubernetes apiserver private key file.";
       default = null;
       type = nullOr path;
     };
 
     tokenAuthFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver token authentication file. See
-        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authentication"/>
+        <https://kubernetes.io/docs/reference/access-authn-authz/authentication>
       '';
       default = null;
       type = nullOr path;
     };
 
     verbosity = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Optional glog verbosity level for logging statements. See
-        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+        <https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md>
       '';
       default = null;
       type = nullOr int;
     };
 
     webhookConfig = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes apiserver Webhook config file. It uses the kubeconfig file format.
-        See <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/webhook/"/>
+        See <https://kubernetes.io/docs/reference/access-authn-authz/webhook/>
       '';
       default = null;
       type = nullOr path;
@@ -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/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index 7c317e94deeb..18c82fc23593 100644
--- a/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -10,62 +10,56 @@ 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
   options.services.kubernetes.controllerManager = with lib.types; {
 
     allocateNodeCIDRs = mkOption {
-      description = "Whether to automatically allocate CIDR ranges for cluster nodes.";
+      description = lib.mdDoc "Whether to automatically allocate CIDR ranges for cluster nodes.";
       default = true;
       type = bool;
     };
 
     bindAddress = mkOption {
-      description = "Kubernetes controller manager listening address.";
+      description = lib.mdDoc "Kubernetes controller manager listening address.";
       default = "127.0.0.1";
       type = str;
     };
 
     clusterCidr = mkOption {
-      description = "Kubernetes CIDR Range for Pods in cluster.";
+      description = lib.mdDoc "Kubernetes CIDR Range for Pods in cluster.";
       default = top.clusterCidr;
       defaultText = literalExpression "config.${otop.clusterCidr}";
       type = str;
     };
 
-    enable = mkEnableOption "Kubernetes controller manager";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes controller manager");
 
     extraOpts = mkOption {
-      description = "Kubernetes controller manager extra command line options.";
+      description = lib.mdDoc "Kubernetes controller manager extra command line options.";
       default = "";
       type = separatedString " ";
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = lib.mdDoc "List set of feature gates";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
-    insecurePort = mkOption {
-      description = "Kubernetes controller manager insecure listening port.";
-      default = 0;
-      type = int;
-    };
-
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes controller manager";
 
     leaderElect = mkOption {
-      description = "Whether to start leader election before executing main loop.";
+      description = lib.mdDoc "Whether to start leader election before executing main loop.";
       type = bool;
       default = true;
     };
 
     rootCaFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes controller manager certificate authority file included in
         service account's token secret.
       '';
@@ -75,13 +69,13 @@ in
     };
 
     securePort = mkOption {
-      description = "Kubernetes controller manager secure listening port.";
+      description = lib.mdDoc "Kubernetes controller manager secure listening port.";
       default = 10252;
       type = int;
     };
 
     serviceAccountKeyFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes controller manager PEM-encoded private RSA key file used to
         sign service account tokens
       '';
@@ -90,21 +84,21 @@ in
     };
 
     tlsCertFile = mkOption {
-      description = "Kubernetes controller-manager certificate file.";
+      description = lib.mdDoc "Kubernetes controller-manager certificate file.";
       default = null;
       type = nullOr path;
     };
 
     tlsKeyFile = mkOption {
-      description = "Kubernetes controller-manager private key file.";
+      description = lib.mdDoc "Kubernetes controller-manager private key file.";
       default = null;
       type = nullOr path;
     };
 
     verbosity = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Optional glog verbosity level for logging statements. See
-        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+        <https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md>
       '';
       default = null;
       type = nullOr int;
@@ -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/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 35ec99d83c84..f5374fc71942 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -77,25 +77,25 @@ let
 
   mkKubeConfigOptions = prefix: {
     server = mkOption {
-      description = "${prefix} kube-apiserver server address.";
+      description = lib.mdDoc "${prefix} kube-apiserver server address.";
       type = types.str;
     };
 
     caFile = mkOption {
-      description = "${prefix} certificate authority file used to connect to kube-apiserver.";
+      description = lib.mdDoc "${prefix} certificate authority file used to connect to kube-apiserver.";
       type = types.nullOr types.path;
       default = cfg.caFile;
       defaultText = literalExpression "config.${opt.caFile}";
     };
 
     certFile = mkOption {
-      description = "${prefix} client certificate file used to connect to kube-apiserver.";
+      description = lib.mdDoc "${prefix} client certificate file used to connect to kube-apiserver.";
       type = types.nullOr types.path;
       default = null;
     };
 
     keyFile = mkOption {
-      description = "${prefix} client key file used to connect to kube-apiserver.";
+      description = lib.mdDoc "${prefix} client key file used to connect to kube-apiserver.";
       type = types.nullOr types.path;
       default = null;
     };
@@ -111,7 +111,7 @@ in {
 
   options.services.kubernetes = {
     roles = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kubernetes role that this machine should take.
 
         Master role will enable etcd, apiserver, scheduler, controller manager
@@ -123,7 +123,7 @@ in {
     };
 
     package = mkOption {
-      description = "Kubernetes package to use.";
+      description = lib.mdDoc "Kubernetes package to use.";
       type = types.package;
       default = pkgs.kubernetes;
       defaultText = literalExpression "pkgs.kubernetes";
@@ -132,7 +132,7 @@ in {
     kubeconfig = mkKubeConfigOptions "Default kubeconfig";
 
     apiserverAddress = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Clusterwide accessible address for the kubernetes apiserver,
         including protocol and optional port.
       '';
@@ -141,49 +141,49 @@ in {
     };
 
     caFile = mkOption {
-      description = "Default kubernetes certificate authority";
+      description = lib.mdDoc "Default kubernetes certificate authority";
       type = types.nullOr types.path;
       default = null;
     };
 
     dataDir = mkOption {
-      description = "Kubernetes root directory for managing kubelet files.";
+      description = lib.mdDoc "Kubernetes root directory for managing kubelet files.";
       default = "/var/lib/kubernetes";
       type = types.path;
     };
 
     easyCerts = mkOption {
-      description = "Automatically setup x509 certificates and keys for the entire cluster.";
+      description = lib.mdDoc "Automatically setup x509 certificates and keys for the entire cluster.";
       default = false;
       type = types.bool;
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates.";
+      description = lib.mdDoc "List set of feature gates.";
       default = [];
       type = types.listOf types.str;
     };
 
     masterAddress = mkOption {
-      description = "Clusterwide available network address or hostname for the kubernetes master server.";
+      description = lib.mdDoc "Clusterwide available network address or hostname for the kubernetes master server.";
       example = "master.example.com";
       type = types.str;
     };
 
     path = mkOption {
-      description = "Packages added to the services' PATH environment variable. Both the bin and sbin subdirectories of each package are added.";
+      description = lib.mdDoc "Packages added to the services' PATH environment variable. Both the bin and sbin subdirectories of each package are added.";
       type = types.listOf types.package;
       default = [];
     };
 
     clusterCidr = mkOption {
-      description = "Kubernetes controller manager and proxy CIDR Range for Pods in cluster.";
+      description = lib.mdDoc "Kubernetes controller manager and proxy CIDR Range for Pods in cluster.";
       default = "10.1.0.0/16";
       type = types.nullOr types.str;
     };
 
     lib = mkOption {
-      description = "Common functions for the kubernetes modules.";
+      description = lib.mdDoc "Common functions for the kubernetes modules.";
       default = {
         inherit mkCert;
         inherit mkKubeConfig;
@@ -193,7 +193,7 @@ in {
     };
 
     secretsPath = mkOption {
-      description = "Default location for kubernetes secrets. Not a store location.";
+      description = lib.mdDoc "Default location for kubernetes secrets. Not a store location.";
       type = types.path;
       default = cfg.dataDir + "/secrets";
       defaultText = literalExpression ''
diff --git a/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixos/modules/services/cluster/kubernetes/flannel.nix
index cb81eaaf0160..53003287fc9c 100644
--- a/nixos/modules/services/cluster/kubernetes/flannel.nix
+++ b/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";
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index af3a5062febc..3ede1cb80e85 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -23,7 +23,11 @@ let
   infraContainer = pkgs.dockerTools.buildImage {
     name = "pause";
     tag = "latest";
-    contents = top.package.pause;
+    copyToRoot = pkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ top.package.pause ];
+    };
     config.Cmd = ["/bin/pause"];
   };
 
@@ -34,17 +38,17 @@ let
   taintOptions = with lib.types; { name, ... }: {
     options = {
       key = mkOption {
-        description = "Key of taint.";
+        description = lib.mdDoc "Key of taint.";
         default = name;
-        defaultText = literalDocBook "Name of this submodule.";
+        defaultText = literalMD "Name of this submodule.";
         type = str;
       };
       value = mkOption {
-        description = "Value of taint.";
+        description = lib.mdDoc "Value of taint.";
         type = str;
       };
       effect = mkOption {
-        description = "Effect of taint.";
+        description = lib.mdDoc "Effect of taint.";
         example = "NoSchedule";
         type = enum ["NoSchedule" "PreferNoSchedule" "NoExecute"];
       };
@@ -58,32 +62,33 @@ in
     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "")
     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "")
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "networkPlugin" ] "")
   ];
 
   ###### interface
   options.services.kubernetes.kubelet = with lib.types; {
 
     address = mkOption {
-      description = "Kubernetes kubelet info server listening address.";
+      description = lib.mdDoc "Kubernetes kubelet info server listening address.";
       default = "0.0.0.0";
       type = str;
     };
 
     clusterDns = mkOption {
-      description = "Use alternative DNS.";
+      description = lib.mdDoc "Use alternative DNS.";
       default = "10.1.0.1";
       type = str;
     };
 
     clusterDomain = mkOption {
-      description = "Use alternative domain.";
+      description = lib.mdDoc "Use alternative domain.";
       default = config.services.kubernetes.addons.dns.clusterDomain;
       defaultText = literalExpression "config.${options.services.kubernetes.addons.dns.clusterDomain}";
       type = str;
     };
 
     clientCaFile = mkOption {
-      description = "Kubernetes apiserver CA file for client authentication.";
+      description = lib.mdDoc "Kubernetes apiserver CA file for client authentication.";
       default = top.caFile;
       defaultText = literalExpression "config.${otop.caFile}";
       type = nullOr path;
@@ -91,13 +96,13 @@ in
 
     cni = {
       packages = mkOption {
-        description = "List of network plugin packages to install.";
+        description = lib.mdDoc "List of network plugin packages to install.";
         type = listOf package;
         default = [];
       };
 
       config = mkOption {
-        description = "Kubernetes CNI configuration.";
+        description = lib.mdDoc "Kubernetes CNI configuration.";
         type = listOf attrs;
         default = [];
         example = literalExpression ''
@@ -123,34 +128,34 @@ in
       };
 
       configDir = mkOption {
-        description = "Path to Kubernetes CNI configuration directory.";
+        description = lib.mdDoc "Path to Kubernetes CNI configuration directory.";
         type = nullOr path;
         default = null;
       };
     };
 
     containerRuntime = mkOption {
-      description = "Which container runtime type to use";
+      description = lib.mdDoc "Which container runtime type to use";
       type = enum ["docker" "remote"];
       default = "remote";
     };
 
     containerRuntimeEndpoint = mkOption {
-      description = "Endpoint at which to find the container runtime api interface/socket";
+      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 = "Kubernetes kubelet extra command line options.";
+      description = lib.mdDoc "Kubernetes kubelet extra command line options.";
       default = "";
       type = separatedString " ";
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = lib.mdDoc "List set of feature gates";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
@@ -158,91 +163,84 @@ in
 
     healthz = {
       bind = mkOption {
-        description = "Kubernetes kubelet healthz listening address.";
+        description = lib.mdDoc "Kubernetes kubelet healthz listening address.";
         default = "127.0.0.1";
         type = str;
       };
 
       port = mkOption {
-        description = "Kubernetes kubelet healthz port.";
+        description = lib.mdDoc "Kubernetes kubelet healthz port.";
         default = 10248;
-        type = int;
+        type = port;
       };
     };
 
     hostname = mkOption {
-      description = "Kubernetes kubelet hostname override.";
-      default = config.networking.hostName;
-      defaultText = literalExpression "config.networking.hostName";
+      description = lib.mdDoc "Kubernetes kubelet hostname override.";
+      defaultText = literalExpression "config.networking.fqdnOrHostName";
       type = str;
     };
 
     kubeconfig = top.lib.mkKubeConfigOptions "Kubelet";
 
     manifests = mkOption {
-      description = "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)";
+      description = lib.mdDoc "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)";
       type = attrsOf attrs;
       default = {};
     };
 
-    networkPlugin = mkOption {
-      description = "Network plugin to use by Kubernetes.";
-      type = nullOr (enum ["cni" "kubenet"]);
-      default = "kubenet";
-    };
-
     nodeIp = mkOption {
-      description = "IP address of the node. If set, kubelet will use this IP address for the node.";
+      description = lib.mdDoc "IP address of the node. If set, kubelet will use this IP address for the node.";
       default = null;
       type = nullOr str;
     };
 
     registerNode = mkOption {
-      description = "Whether to auto register kubelet with API server.";
+      description = lib.mdDoc "Whether to auto register kubelet with API server.";
       default = true;
       type = bool;
     };
 
     port = mkOption {
-      description = "Kubernetes kubelet info server listening port.";
+      description = lib.mdDoc "Kubernetes kubelet info server listening port.";
       default = 10250;
-      type = int;
+      type = port;
     };
 
     seedDockerImages = mkOption {
-      description = "List of docker images to preload on system";
+      description = lib.mdDoc "List of docker images to preload on system";
       default = [];
       type = listOf package;
     };
 
     taints = mkOption {
-      description = "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/).";
+      description = lib.mdDoc "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/).";
       default = {};
       type = attrsOf (submodule [ taintOptions ]);
     };
 
     tlsCertFile = mkOption {
-      description = "File containing x509 Certificate for HTTPS.";
+      description = lib.mdDoc "File containing x509 Certificate for HTTPS.";
       default = null;
       type = nullOr path;
     };
 
     tlsKeyFile = mkOption {
-      description = "File containing x509 private key matching tlsCertFile.";
+      description = lib.mdDoc "File containing x509 private key matching tlsCertFile.";
       default = null;
       type = nullOr path;
     };
 
     unschedulable = mkOption {
-      description = "Whether to set node taint to unschedulable=true as it is the case of node that has only master role.";
+      description = lib.mdDoc "Whether to set node taint to unschedulable=true as it is the case of node that has only master role.";
       default = false;
       type = bool;
     };
 
     verbosity = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Optional glog verbosity level for logging statements. See
-        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+        <https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md>
       '';
       default = null;
       type = nullOr int;
@@ -311,7 +309,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 \
@@ -319,8 +316,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 \
@@ -353,8 +348,8 @@ in
 
       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/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 7d9198d20e8c..26fe0f5e9e09 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -41,16 +41,16 @@ in
   ###### interface
   options.services.kubernetes.pki = with lib.types; {
 
-    enable = mkEnableOption "easyCert issuer service";
+    enable = mkEnableOption (lib.mdDoc "easyCert issuer service");
 
     certs = mkOption {
-      description = "List of certificate specs to feed to cert generator.";
+      description = lib.mdDoc "List of certificate specs to feed to cert generator.";
       default = {};
       type = attrs;
     };
 
     genCfsslCACert = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to automatically generate cfssl CA certificate and key,
         if they don't exist.
       '';
@@ -59,7 +59,7 @@ in
     };
 
     genCfsslAPICerts = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to automatically generate cfssl API webserver TLS cert and key,
         if they don't exist.
       '';
@@ -68,7 +68,7 @@ in
     };
 
     cfsslAPIExtraSANs = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Extra x509 Subject Alternative Names to be added to the cfssl API webserver TLS cert.
       '';
       default = [];
@@ -77,7 +77,7 @@ in
     };
 
     genCfsslAPIToken = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to automatically generate cfssl API-token secret,
         if they doesn't exist.
       '';
@@ -86,13 +86,13 @@ in
     };
 
     pkiTrustOnBootstrap = mkOption {
-      description = "Whether to always trust remote cfssl server upon initial PKI bootstrap.";
+      description = lib.mdDoc "Whether to always trust remote cfssl server upon initial PKI bootstrap.";
       default = true;
       type = bool;
     };
 
     caCertPathPrefix = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Path-prefrix for the CA-certificate to be used for cfssl signing.
         Suffixes ".pem" and "-key.pem" will be automatically appended for
         the public and private keys respectively.
@@ -103,7 +103,7 @@ in
     };
 
     caSpec = mkOption {
-      description = "Certificate specification for the auto-generated CAcert.";
+      description = lib.mdDoc "Certificate specification for the auto-generated CAcert.";
       default = {
         CN = "kubernetes-cluster-ca";
         O = "NixOS";
@@ -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,7 +266,7 @@ in
           in
           ''
             export KUBECONFIG=${clusterAdminKubeconfig}
-            ${kubernetes}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
+            ${top.package}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
           '';
         })]);
 
@@ -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/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixos/modules/services/cluster/kubernetes/proxy.nix
index 0fd98d1c1576..015784f7e311 100644
--- a/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -16,28 +16,28 @@ in
   options.services.kubernetes.proxy = with lib.types; {
 
     bindAddress = mkOption {
-      description = "Kubernetes proxy listening address.";
+      description = lib.mdDoc "Kubernetes proxy listening address.";
       default = "0.0.0.0";
       type = str;
     };
 
-    enable = mkEnableOption "Kubernetes proxy";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes proxy");
 
     extraOpts = mkOption {
-      description = "Kubernetes proxy extra command line options.";
+      description = lib.mdDoc "Kubernetes proxy extra command line options.";
       default = "";
       type = separatedString " ";
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = lib.mdDoc "List set of feature gates";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
     };
 
     hostname = mkOption {
-      description = "Kubernetes proxy hostname override.";
+      description = lib.mdDoc "Kubernetes proxy hostname override.";
       default = config.networking.hostName;
       defaultText = literalExpression "config.networking.hostName";
       type = str;
@@ -46,9 +46,9 @@ in
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes proxy";
 
     verbosity = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Optional glog verbosity level for logging statements. See
-        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+        <https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md>
       '';
       default = null;
       type = nullOr int;
diff --git a/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixos/modules/services/cluster/kubernetes/scheduler.nix
index 2d95528a6ead..f31a92f36840 100644
--- a/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -12,21 +12,21 @@ in
   options.services.kubernetes.scheduler = with lib.types; {
 
     address = mkOption {
-      description = "Kubernetes scheduler listening address.";
+      description = lib.mdDoc "Kubernetes scheduler listening address.";
       default = "127.0.0.1";
       type = str;
     };
 
-    enable = mkEnableOption "Kubernetes scheduler";
+    enable = mkEnableOption (lib.mdDoc "Kubernetes scheduler");
 
     extraOpts = mkOption {
-      description = "Kubernetes scheduler extra command line options.";
+      description = lib.mdDoc "Kubernetes scheduler extra command line options.";
       default = "";
       type = separatedString " ";
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = lib.mdDoc "List set of feature gates";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
       type = listOf str;
@@ -35,21 +35,21 @@ in
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes scheduler";
 
     leaderElect = mkOption {
-      description = "Whether to start leader election before executing main loop.";
+      description = lib.mdDoc "Whether to start leader election before executing main loop.";
       type = bool;
       default = true;
     };
 
     port = mkOption {
-      description = "Kubernetes scheduler listening port.";
+      description = lib.mdDoc "Kubernetes scheduler listening port.";
       default = 10251;
-      type = int;
+      type = port;
     };
 
     verbosity = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Optional glog verbosity level for logging statements. See
-        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+        <https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md>
       '';
       default = null;
       type = nullOr int;
diff --git a/nixos/modules/services/cluster/pacemaker/default.nix b/nixos/modules/services/cluster/pacemaker/default.nix
index 7eeadffcc586..0f37f4b754fe 100644
--- a/nixos/modules/services/cluster/pacemaker/default.nix
+++ b/nixos/modules/services/cluster/pacemaker/default.nix
@@ -7,13 +7,13 @@ in
 {
   # interface
   options.services.pacemaker = {
-    enable = mkEnableOption "pacemaker";
+    enable = mkEnableOption (lib.mdDoc "pacemaker");
 
     package = mkOption {
       type = types.package;
       default = pkgs.pacemaker;
       defaultText = literalExpression "pkgs.pacemaker";
-      description = "Package that should be used for pacemaker.";
+      description = lib.mdDoc "Package that should be used for pacemaker.";
     };
   };
 
diff --git a/nixos/modules/services/cluster/patroni/default.nix b/nixos/modules/services/cluster/patroni/default.nix
new file mode 100644
index 000000000000..83b372f59497
--- /dev/null
+++ b/nixos/modules/services/cluster/patroni/default.nix
@@ -0,0 +1,268 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.patroni;
+  defaultUser = "patroni";
+  defaultGroup = "patroni";
+  format = pkgs.formats.yaml { };
+
+  #boto doesn't support python 3.10 yet
+  patroni = pkgs.patroni.override { pythonPackages = pkgs.python39Packages; };
+
+  configFileName = "patroni-${cfg.scope}-${cfg.name}.yaml";
+  configFile = format.generate configFileName cfg.settings;
+in
+{
+  options.services.patroni = {
+
+    enable = mkEnableOption (lib.mdDoc "Patroni");
+
+    postgresqlPackage = mkOption {
+      type = types.package;
+      example = literalExpression "pkgs.postgresql_14";
+      description = mdDoc ''
+        PostgreSQL package to use.
+        Plugins can be enabled like this `pkgs.postgresql_14.withPackages (p: [ p.pg_safeupdate p.postgis ])`.
+      '';
+    };
+
+    postgresqlDataDir = mkOption {
+      type = types.path;
+      defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.patroni.postgresqlPackage.psqlSchema}"'';
+      example = "/var/lib/postgresql/14";
+      default = "/var/lib/postgresql/${cfg.postgresqlPackage.psqlSchema}";
+      description = mdDoc ''
+        The data directory for PostgreSQL. If left as the default value
+        this directory will automatically be created before the PostgreSQL server starts, otherwise
+        the sysadmin is responsible for ensuring the directory exists with appropriate ownership
+        and permissions.
+      '';
+    };
+
+    postgresqlPort = mkOption {
+      type = types.port;
+      default = 5432;
+      description = mdDoc ''
+        The port on which PostgreSQL listens.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = defaultUser;
+      example = "postgres";
+      description = mdDoc ''
+        The user for the service. If left as the default value this user will automatically be created,
+        otherwise the sysadmin is responsible for ensuring the user exists.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = defaultGroup;
+      example = "postgres";
+      description = mdDoc ''
+        The group for the service. If left as the default value this group will automatically be created,
+        otherwise the sysadmin is responsible for ensuring the group exists.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/patroni";
+      description = mdDoc ''
+        Folder where Patroni data will be written, used by Raft as well if enabled.
+      '';
+    };
+
+    scope = mkOption {
+      type = types.str;
+      example = "cluster1";
+      description = mdDoc ''
+        Cluster name.
+      '';
+    };
+
+    name = mkOption {
+      type = types.str;
+      example = "node1";
+      description = mdDoc ''
+        The name of the host. Must be unique for the cluster.
+      '';
+    };
+
+    namespace = mkOption {
+      type = types.str;
+      default = "/service";
+      description = mdDoc ''
+        Path within the configuration store where Patroni will keep information about the cluster.
+      '';
+    };
+
+    nodeIp = mkOption {
+      type = types.str;
+      example = "192.168.1.1";
+      description = mdDoc ''
+        IP address of this node.
+      '';
+    };
+
+    otherNodesIps = mkOption {
+      type = types.listOf types.string;
+      example = [ "192.168.1.2" "192.168.1.3" ];
+      description = mdDoc ''
+        IP addresses of the other nodes.
+      '';
+    };
+
+    restApiPort = mkOption {
+      type = types.port;
+      default = 8008;
+      description = mdDoc ''
+        The port on Patroni's REST api listens.
+      '';
+    };
+
+    raft = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        This will configure Patroni to use its own RAFT implementation instead of using a dedicated DCS.
+      '';
+    };
+
+    raftPort = mkOption {
+      type = types.port;
+      default = 5010;
+      description = mdDoc ''
+        The port on which RAFT listens.
+      '';
+    };
+
+    softwareWatchdog = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        This will configure Patroni to use the software watchdog built into the Linux kernel
+        as described in the [documentation](https://patroni.readthedocs.io/en/latest/watchdog.html#setting-up-software-watchdog-on-linux).
+      '';
+    };
+
+    settings = mkOption {
+      type = format.type;
+      default = { };
+      description = mdDoc ''
+        The primary patroni configuration. See the [documentation](https://patroni.readthedocs.io/en/latest/SETTINGS.html)
+        for possible values.
+        Secrets should be passed in by using the `environmentFiles` option.
+      '';
+    };
+
+    environmentFiles = mkOption {
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+      default = { };
+      example = {
+        PATRONI_REPLICATION_PASSWORD = "/secret/file";
+        PATRONI_SUPERUSER_PASSWORD = "/secret/file";
+      };
+      description = mdDoc "Environment variables made available to Patroni as files content, useful for providing secrets from files.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.patroni.settings = {
+      scope = cfg.scope;
+      name = cfg.name;
+      namespace = cfg.namespace;
+
+      restapi = {
+        listen = "${cfg.nodeIp}:${toString cfg.restApiPort}";
+        connect_address = "${cfg.nodeIp}:${toString cfg.restApiPort}";
+      };
+
+      raft = mkIf cfg.raft {
+        data_dir = "${cfg.dataDir}/raft";
+        self_addr = "${cfg.nodeIp}:5010";
+        partner_addrs = map (ip: ip + ":5010") cfg.otherNodesIps;
+      };
+
+      postgresql = {
+        listen = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
+        connect_address = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
+        data_dir = cfg.postgresqlDataDir;
+        bin_dir = "${cfg.postgresqlPackage}/bin";
+        pgpass = "${cfg.dataDir}/pgpass";
+      };
+
+      watchdog = mkIf cfg.softwareWatchdog {
+        mode = "required";
+        device = "/dev/watchdog";
+        safety_margin = 5;
+      };
+    };
+
+
+    users = {
+      users = mkIf (cfg.user == defaultUser) {
+        patroni = {
+          group = cfg.group;
+          isSystemUser = true;
+        };
+      };
+      groups = mkIf (cfg.group == defaultGroup) {
+        patroni = { };
+      };
+    };
+
+    systemd.services = {
+      patroni = {
+        description = "Runners to orchestrate a high-availability PostgreSQL";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        script = ''
+          ${concatStringsSep "\n" (attrValues (mapAttrs (name: path: ''export ${name}="$(< ${escapeShellArg path})"'') cfg.environmentFiles))}
+          exec ${patroni}/bin/patroni ${configFile}
+        '';
+
+        serviceConfig = mkMerge [
+          {
+            User = cfg.user;
+            Group = cfg.group;
+            Type = "simple";
+            Restart = "on-failure";
+            TimeoutSec = 30;
+            ExecReload = "${pkgs.coreutils}/bin/kill -s HUP $MAINPID";
+            KillMode = "process";
+          }
+          (mkIf (cfg.postgresqlDataDir == "/var/lib/postgresql/${cfg.postgresqlPackage.psqlSchema}" && cfg.dataDir == "/var/lib/patroni") {
+            StateDirectory = "patroni patroni/raft postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}";
+            StateDirectoryMode = "0750";
+          })
+        ];
+      };
+    };
+
+    boot.kernelModules = mkIf cfg.softwareWatchdog [ "softdog" ];
+
+    services.udev.extraRules = mkIf cfg.softwareWatchdog ''
+      KERNEL=="watchdog", OWNER="${cfg.user}", GROUP="${cfg.group}", MODE="0600"
+    '';
+
+    environment.systemPackages = [
+      patroni
+      cfg.postgresqlPackage
+      (mkIf cfg.raft pkgs.python310Packages.pysyncobj)
+    ];
+
+    environment.etc."${configFileName}".source = configFile;
+
+    environment.sessionVariables = {
+      PATRONICTL_CONFIG_FILE = "/etc/${configFileName}";
+    };
+  };
+
+  meta.maintainers = [ maintainers.phfroidmont ];
+}
diff --git a/nixos/modules/services/cluster/spark/default.nix b/nixos/modules/services/cluster/spark/default.nix
index e6b44e130a3e..bf39c5537332 100644
--- a/nixos/modules/services/cluster/spark/default.nix
+++ b/nixos/modules/services/cluster/spark/default.nix
@@ -7,16 +7,16 @@ with lib;
   options = {
     services.spark = {
       master = {
-        enable = mkEnableOption "Spark master service";
+        enable = mkEnableOption (lib.mdDoc "Spark master service");
         bind = mkOption {
           type = types.str;
-          description = "Address the spark master binds to.";
+          description = lib.mdDoc "Address the spark master binds to.";
           default = "127.0.0.1";
           example = "0.0.0.0";
         };
         restartIfChanged  = mkOption {
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Automatically restart master service on config change.
             This can be set to false to defer restarts on clusters running critical applications.
             Please consider the security implications of inadvertently running an older version,
@@ -26,7 +26,7 @@ with lib;
         };
         extraEnvironment = mkOption {
           type = types.attrsOf types.str;
-          description = "Extra environment variables to pass to spark master. See spark-standalone documentation.";
+          description = lib.mdDoc "Extra environment variables to pass to spark master. See spark-standalone documentation.";
           default = {};
           example = {
             SPARK_MASTER_WEBUI_PORT = 8181;
@@ -35,20 +35,20 @@ with lib;
         };
       };
       worker = {
-        enable = mkEnableOption "Spark worker service";
+        enable = mkEnableOption (lib.mdDoc "Spark worker service");
         workDir = mkOption {
           type = types.path;
-          description = "Spark worker work dir.";
+          description = lib.mdDoc "Spark worker work dir.";
           default = "/var/lib/spark";
         };
         master = mkOption {
           type = types.str;
-          description = "Address of the spark master.";
+          description = lib.mdDoc "Address of the spark master.";
           default = "127.0.0.1:7077";
         };
         restartIfChanged  = mkOption {
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Automatically restart worker service on config change.
             This can be set to false to defer restarts on clusters running critical applications.
             Please consider the security implications of inadvertently running an older version,
@@ -58,7 +58,7 @@ with lib;
         };
         extraEnvironment = mkOption {
           type = types.attrsOf types.str;
-          description = "Extra environment variables to pass to spark worker.";
+          description = lib.mdDoc "Extra environment variables to pass to spark worker.";
           default = {};
           example = {
             SPARK_WORKER_CORES = 5;
@@ -68,18 +68,18 @@ with lib;
       };
       confDir = mkOption {
         type = types.path;
-        description = "Spark configuration directory. Spark will use the configuration files (spark-defaults.conf, spark-env.sh, log4j.properties, etc) from this directory.";
+        description = lib.mdDoc "Spark configuration directory. Spark will use the configuration files (spark-defaults.conf, spark-env.sh, log4j.properties, etc) from this directory.";
         default = "${cfg.package}/lib/${cfg.package.untarDir}/conf";
         defaultText = literalExpression ''"''${package}/lib/''${package.untarDir}/conf"'';
       };
       logDir = mkOption {
         type = types.path;
-        description = "Spark log directory.";
+        description = lib.mdDoc "Spark log directory.";
         default = "/var/log/spark";
       };
       package = mkOption {
         type = types.package;
-        description = "Spark package.";
+        description = lib.mdDoc "Spark package.";
         default = pkgs.spark;
         defaultText = literalExpression "pkgs.spark";
         example = literalExpression ''pkgs.spark.overrideAttrs (super: rec {
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 52249455fd45..5fb715f4d779 100644
--- a/nixos/modules/services/computing/boinc/client.nix
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the BOINC distributed computing client. If this
           option is set to true, the boinc_client daemon will be run as a
           background service. The boinccmd command can be used to control the
@@ -31,7 +31,7 @@ in
         type = types.package;
         default = pkgs.boinc;
         defaultText = literalExpression "pkgs.boinc";
-        description = ''
+        description = lib.mdDoc ''
           Which BOINC package to use.
         '';
       };
@@ -39,7 +39,7 @@ in
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/boinc";
-        description = ''
+        description = lib.mdDoc ''
           The directory in which to store BOINC's configuration and data files.
         '';
       };
@@ -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/nixos/modules/services/computing/foldingathome/client.nix b/nixos/modules/services/computing/foldingathome/client.nix
index aa9d0a5218fa..1229e5ac987e 100644
--- a/nixos/modules/services/computing/foldingathome/client.nix
+++ b/nixos/modules/services/computing/foldingathome/client.nix
@@ -18,13 +18,13 @@ in
     '')
   ];
   options.services.foldingathome = {
-    enable = mkEnableOption "Enable the Folding@home client";
+    enable = mkEnableOption (lib.mdDoc "Folding@home client");
 
     package = mkOption {
       type = types.package;
       default = pkgs.fahclient;
       defaultText = literalExpression "pkgs.fahclient";
-      description = ''
+      description = lib.mdDoc ''
         Which Folding@home client to use.
       '';
     };
@@ -32,7 +32,7 @@ in
     user = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The user associated with the reported computation results. This will
         be used in the ranking statistics.
       '';
@@ -41,7 +41,7 @@ in
     team = mkOption {
       type = types.int;
       default = 236565;
-      description = ''
+      description = lib.mdDoc ''
         The team ID associated with the reported computation results. This
         will be used in the ranking statistics.
 
@@ -52,7 +52,7 @@ in
     daemonNiceLevel = mkOption {
       type = types.ints.between (-20) 19;
       default = 0;
-      description = ''
+      description = lib.mdDoc ''
         Daemon process priority for FAHClient.
         0 is the default Unix process priority, 19 is the lowest.
       '';
@@ -61,9 +61,9 @@ in
     extraArgs = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Extra startup options for the FAHClient. Run
-        <literal>FAHClient --help</literal> to find all the available options.
+        `FAHClient --help` to find all the available options.
       '';
     };
   };
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 8cbe54c60604..0c80e79d4b79 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -66,25 +66,25 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable the slurm control daemon.
             Note that the standard authentication method is "munge".
             The "munge" service needs to be provided with a password file in order for
-            slurm to work properly (see <literal>services.munge.password</literal>).
+            slurm to work properly (see `services.munge.password`).
           '';
         };
       };
 
       dbdserver = {
-        enable = mkEnableOption "SlurmDBD service";
+        enable = mkEnableOption (lib.mdDoc "SlurmDBD service");
 
         dbdHost = mkOption {
           type = types.str;
           default = config.networking.hostName;
           defaultText = literalExpression "config.networking.hostName";
-          description = ''
-            Hostname of the machine where <literal>slurmdbd</literal>
-            is running (i.e. name returned by <literal>hostname -s</literal>).
+          description = lib.mdDoc ''
+            Hostname of the machine where `slurmdbd`
+            is running (i.e. name returned by `hostname -s`).
           '';
         };
 
@@ -92,7 +92,7 @@ in
           type = types.str;
           default = cfg.user;
           defaultText = literalExpression "config.${opt.user}";
-          description = ''
+          description = lib.mdDoc ''
             Database user name.
           '';
         };
@@ -100,34 +100,33 @@ in
         storagePassFile = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Path to file with database password. The content of this will be used to
-            create the password for the <literal>StoragePass</literal> option.
+            create the password for the `StoragePass` option.
           '';
         };
 
         extraConfig = mkOption {
           type = types.lines;
           default = "";
-          description = ''
-            Extra configuration for <literal>slurmdbd.conf</literal> See also:
-            <citerefentry><refentrytitle>slurmdbd.conf</refentrytitle>
-            <manvolnum>8</manvolnum></citerefentry>.
+          description = lib.mdDoc ''
+            Extra configuration for `slurmdbd.conf` See also:
+            {manpage}`slurmdbd.conf(8)`.
           '';
         };
       };
 
       client = {
-        enable = mkEnableOption "slurm client daemon";
+        enable = mkEnableOption (lib.mdDoc "slurm client daemon");
       };
 
       enableStools = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to provide a slurm.conf file.
           Enable this option if you do not run a slurm daemon on this host
-          (i.e. <literal>server.enable</literal> and <literal>client.enable</literal> are <literal>false</literal>)
+          (i.e. `server.enable` and `client.enable` are `false`)
           but you still want to run slurm commands from this host.
         '';
       };
@@ -137,7 +136,7 @@ in
         default = pkgs.slurm.override { enableX11 = ! cfg.enableSrunX11; };
         defaultText = literalExpression "pkgs.slurm";
         example = literalExpression "pkgs.slurm-full";
-        description = ''
+        description = lib.mdDoc ''
           The package to use for slurm binaries.
         '';
       };
@@ -146,7 +145,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = null;
-        description = ''
+        description = lib.mdDoc ''
           The short hostname of the machine where SLURM control functions are
           executed (i.e. the name returned by the command "hostname -s", use "tux001"
           rather than "tux001.my.com").
@@ -158,7 +157,7 @@ in
         default = cfg.controlMachine;
         defaultText = literalExpression "config.${opt.controlMachine}";
         example = null;
-        description = ''
+        description = lib.mdDoc ''
           Name that ControlMachine should be referred to in establishing a
           communications path.
         '';
@@ -168,7 +167,7 @@ in
         type = types.str;
         default = "default";
         example = "myCluster";
-        description = ''
+        description = lib.mdDoc ''
           Necessary to distinguish accounting records in a multi-cluster environment.
         '';
       };
@@ -177,7 +176,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = literalExpression ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
-        description = ''
+        description = lib.mdDoc ''
           Name that SLURM uses to refer to a node (or base partition for BlueGene
           systems). Typically this would be the string that "/bin/hostname -s"
           returns. Note that now you have to write node's parameters after the name.
@@ -188,7 +187,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = literalExpression ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
-        description = ''
+        description = lib.mdDoc ''
           Name by which the partition may be referenced. Note that now you have
           to write the partition's parameters after the name.
         '';
@@ -197,17 +196,17 @@ in
       enableSrunX11 = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If enabled srun will accept the option "--x11" to allow for X11 forwarding
           from within an interactive session or a batch job. This activates the
           slurm-spank-x11 module. Note that this option also enables
-          <option>services.openssh.forwardX11</option> on the client.
+          {option}`services.openssh.forwardX11` on the client.
 
           This option requires slurm to be compiled without native X11 support.
           The default behavior is to re-compile the slurm package with native X11
           support disabled if this option is set to true.
 
-          To use the native X11 support add <literal>PrologFlags=X11</literal> in <option>extraConfig</option>.
+          To use the native X11 support add `PrologFlags=X11` in {option}`extraConfig`.
           Note that this method will only work RSA SSH host keys.
         '';
       };
@@ -215,7 +214,7 @@ in
       procTrackType = mkOption {
         type = types.str;
         default = "proctrack/linuxproc";
-        description = ''
+        description = lib.mdDoc ''
           Plugin to be used for process tracking on a job step basis.
           The slurmd daemon uses this mechanism to identify all processes
           which are children of processes it spawns for a user job step.
@@ -225,7 +224,7 @@ in
       stateSaveLocation = mkOption {
         type = types.str;
         default = "/var/spool/slurmctld";
-        description = ''
+        description = lib.mdDoc ''
           Directory into which the Slurm controller, slurmctld, saves its state.
         '';
       };
@@ -233,7 +232,7 @@ in
       user = mkOption {
         type = types.str;
         default = defaultUser;
-        description = ''
+        description = lib.mdDoc ''
           Set this option when you want to run the slurmctld daemon
           as something else than the default slurm user "slurm".
           Note that the UID of this user needs to be the same
@@ -244,7 +243,7 @@ in
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options that will be added verbatim at
           the end of the slurm configuration file.
         '';
@@ -253,28 +252,28 @@ in
       extraPlugstackConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
-          Extra configuration that will be added to the end of <literal>plugstack.conf</literal>.
+        description = lib.mdDoc ''
+          Extra configuration that will be added to the end of `plugstack.conf`.
         '';
       };
 
       extraCgroupConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
-          Extra configuration for <literal>cgroup.conf</literal>. This file is
-          used when <literal>procTrackType=proctrack/cgroup</literal>.
+        description = lib.mdDoc ''
+          Extra configuration for `cgroup.conf`. This file is
+          used when `procTrackType=proctrack/cgroup`.
         '';
       };
 
       extraConfigPaths = mkOption {
         type = with types; listOf path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Slurm expects config files for plugins in the same path
-          as <literal>slurm.conf</literal>. Add extra nix store
+          as `slurm.conf`. Add extra nix store
           paths that should be merged into same directory as
-          <literal>slurm.conf</literal>.
+          `slurm.conf`.
         '';
       };
 
@@ -282,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.
         '';
@@ -361,8 +360,13 @@ in
         ++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "systemd-tmpfiles-clean.service" ];
-      requires = [ "network.target" ];
+      after = [
+        "systemd-tmpfiles-clean.service"
+        "munge.service"
+        "network-online.target"
+        "remote-fs.target"
+      ];
+      wants = [ "network-online.target" ];
 
       serviceConfig = {
         Type = "forking";
@@ -371,6 +375,7 @@ in
         PIDFile = "/run/slurmd.pid";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         LimitMEMLOCK = "infinity";
+        Delegate="Yes";
       };
     };
 
diff --git a/nixos/modules/services/computing/torque/mom.nix b/nixos/modules/services/computing/torque/mom.nix
index 6747bd4b0d5a..5dd41429bf81 100644
--- a/nixos/modules/services/computing/torque/mom.nix
+++ b/nixos/modules/services/computing/torque/mom.nix
@@ -17,11 +17,11 @@ in
   options = {
 
     services.torque.mom = {
-      enable = mkEnableOption "torque computing node";
+      enable = mkEnableOption (lib.mdDoc "torque computing node");
 
       serverNode = mkOption {
         type = types.str;
-        description = "Hostname running pbs server.";
+        description = lib.mdDoc "Hostname running pbs server.";
       };
 
     };
diff --git a/nixos/modules/services/computing/torque/server.nix b/nixos/modules/services/computing/torque/server.nix
index 8d923fc04d46..02f20fb37c10 100644
--- a/nixos/modules/services/computing/torque/server.nix
+++ b/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/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index 80c6c6abfd0b..5666199c4845 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/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, ... }:
 
@@ -10,7 +10,7 @@ let
 
   python = cfg.package.pythonModule;
 
-  escapeStr = s: escape ["'"] s;
+  escapeStr = escape [ "'" ];
 
   defaultMasterCfg = pkgs.writeText "master.cfg" ''
     from buildbot.plugins import *
@@ -61,7 +61,7 @@ in {
 
       factorySteps = mkOption {
         type = types.listOf types.str;
-        description = "Factory Steps";
+        description = lib.mdDoc "Factory Steps";
         default = [];
         example = [
           "steps.Git(repourl='https://github.com/buildbot/pyflakes.git', mode='incremental')"
@@ -71,7 +71,7 @@ in {
 
       changeSource = mkOption {
         type = types.listOf types.str;
-        description = "List of Change Sources.";
+        description = lib.mdDoc "List of Change Sources.";
         default = [];
         example = [
           "changes.GitPoller('https://github.com/buildbot/pyflakes.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
@@ -81,26 +81,26 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Buildbot continuous integration server.";
+        description = lib.mdDoc "Whether to enable the Buildbot continuous integration server.";
       };
 
       extraConfig = mkOption {
         type = types.str;
-        description = "Extra configuration to append to master.cfg";
+        description = lib.mdDoc "Extra configuration to append to master.cfg";
         default = "c['buildbotNetUsageData'] = None";
       };
 
       masterCfg = mkOption {
         type = types.path;
-        description = "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
+        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";
       };
 
       schedulers = mkOption {
         type = types.listOf types.str;
-        description = "List of Schedulers.";
+        description = lib.mdDoc "List of Schedulers.";
         default = [
           "schedulers.SingleBranchScheduler(name='all', change_filter=util.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=['runtests'])"
           "schedulers.ForceScheduler(name='force',builderNames=['runtests'])"
@@ -109,7 +109,7 @@ in {
 
       builders = mkOption {
         type = types.listOf types.str;
-        description = "List of Builders.";
+        description = lib.mdDoc "List of Builders.";
         default = [
           "util.BuilderConfig(name='runtests',workernames=['example-worker'],factory=factory)"
         ];
@@ -117,52 +117,52 @@ in {
 
       workers = mkOption {
         type = types.listOf types.str;
-        description = "List of Workers.";
+        description = lib.mdDoc "List of Workers.";
         default = [ "worker.Worker('example-worker', 'pass')" ];
       };
 
       reporters = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = "List of reporter objects used to present build status to various users.";
+        description = lib.mdDoc "List of reporter objects used to present build status to various users.";
       };
 
       user = mkOption {
         default = "buildbot";
         type = types.str;
-        description = "User the buildbot server should execute under.";
+        description = lib.mdDoc "User the buildbot server should execute under.";
       };
 
       group = mkOption {
         default = "buildbot";
         type = types.str;
-        description = "Primary group of buildbot user.";
+        description = lib.mdDoc "Primary group of buildbot user.";
       };
 
       extraGroups = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "List of extra groups that the buildbot user should be a part of.";
+        description = lib.mdDoc "List of extra groups that the buildbot user should be a part of.";
       };
 
       home = mkOption {
         default = "/home/buildbot";
         type = types.path;
-        description = "Buildbot home directory.";
+        description = lib.mdDoc "Buildbot home directory.";
       };
 
       buildbotDir = mkOption {
         default = "${cfg.home}/master";
         defaultText = literalExpression ''"''${config.${opt.home}}/master"'';
         type = types.path;
-        description = "Specifies the Buildbot directory.";
+        description = lib.mdDoc "Specifies the Buildbot directory.";
       };
 
       pbPort = mkOption {
         default = 9989;
         type = types.either types.str types.int;
         example = "'tcp:9990:interface=127.0.0.1'";
-        description = ''
+        description = lib.mdDoc ''
           The buildmaster will listen on a TCP port of your choosing
           for connections from workers.
           It can also use this port for connections from remote Change Sources,
@@ -170,51 +170,51 @@ in {
           This port should be visible to the outside world, and you’ll need to tell
           your worker admins about your choice.
           If put in (single) quotes, this can also be used as a connection string,
-          as defined in the <link xlink:href="https://twistedmatrix.com/documents/current/core/howto/endpoints.html">ConnectionStrings guide</link>.
+          as defined in the [ConnectionStrings guide](https://twistedmatrix.com/documents/current/core/howto/endpoints.html).
         '';
       };
 
       listenAddress = mkOption {
         default = "0.0.0.0";
         type = types.str;
-        description = "Specifies the bind address on which the buildbot HTTP interface listens.";
+        description = lib.mdDoc "Specifies the bind address on which the buildbot HTTP interface listens.";
       };
 
       buildbotUrl = mkOption {
         default = "http://localhost:8010/";
         type = types.str;
-        description = "Specifies the Buildbot URL.";
+        description = lib.mdDoc "Specifies the Buildbot URL.";
       };
 
       title = mkOption {
         default = "Buildbot";
         type = types.str;
-        description = "Specifies the Buildbot Title.";
+        description = lib.mdDoc "Specifies the Buildbot Title.";
       };
 
       titleUrl = mkOption {
         default = "Buildbot";
         type = types.str;
-        description = "Specifies the Buildbot TitleURL.";
+        description = lib.mdDoc "Specifies the Buildbot TitleURL.";
       };
 
       dbUrl = mkOption {
         default = "sqlite:///state.sqlite";
         type = types.str;
-        description = "Specifies the database connection string.";
+        description = lib.mdDoc "Specifies the database connection string.";
       };
 
       port = mkOption {
         default = 8010;
-        type = types.int;
-        description = "Specifies port number on which the buildbot HTTP interface listens.";
+        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";
-        description = "Package to use for buildbot.";
+        description = lib.mdDoc "Package to use for buildbot.";
         example = literalExpression "pkgs.python3Packages.buildbot";
       };
 
@@ -222,14 +222,14 @@ in {
         default = [ pkgs.git ];
         defaultText = literalExpression "[ pkgs.git ]";
         type = types.listOf types.package;
-        description = "Packages to add to PATH for the buildbot process.";
+        description = lib.mdDoc "Packages to add to PATH for the buildbot process.";
       };
 
       pythonPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
         default = pythonPackages: with pythonPackages; [ ];
         defaultText = literalExpression "pythonPackages: with pythonPackages; [ ]";
-        description = "Packages to add the to the PYTHONPATH of the buildbot process.";
+        description = lib.mdDoc "Packages to add the to the PYTHONPATH of the buildbot process.";
         example = literalExpression "pythonPackages: with pythonPackages; [ requests ]";
       };
     };
@@ -245,9 +245,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;
       };
     };
diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 1d7f53bb6559..52c41c4a7584 100644
--- a/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -49,89 +49,89 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Buildbot Worker.";
+        description = lib.mdDoc "Whether to enable the Buildbot Worker.";
       };
 
       user = mkOption {
         default = "bbworker";
         type = types.str;
-        description = "User the buildbot Worker should execute under.";
+        description = lib.mdDoc "User the buildbot Worker should execute under.";
       };
 
       group = mkOption {
         default = "bbworker";
         type = types.str;
-        description = "Primary group of buildbot Worker user.";
+        description = lib.mdDoc "Primary group of buildbot Worker user.";
       };
 
       extraGroups = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "List of extra groups that the Buildbot Worker user should be a part of.";
+        description = lib.mdDoc "List of extra groups that the Buildbot Worker user should be a part of.";
       };
 
       home = mkOption {
         default = "/home/bbworker";
         type = types.path;
-        description = "Buildbot home directory.";
+        description = lib.mdDoc "Buildbot home directory.";
       };
 
       buildbotDir = mkOption {
         default = "${cfg.home}/worker";
         defaultText = literalExpression ''"''${config.${opt.home}}/worker"'';
         type = types.path;
-        description = "Specifies the Buildbot directory.";
+        description = lib.mdDoc "Specifies the Buildbot directory.";
       };
 
       workerUser = mkOption {
         default = "example-worker";
         type = types.str;
-        description = "Specifies the Buildbot Worker user.";
+        description = lib.mdDoc "Specifies the Buildbot Worker user.";
       };
 
       workerPass = mkOption {
         default = "pass";
         type = types.str;
-        description = "Specifies the Buildbot Worker password.";
+        description = lib.mdDoc "Specifies the Buildbot Worker password.";
       };
 
       workerPassFile = mkOption {
         type = types.path;
-        description = "File used to store the Buildbot Worker password";
+        description = lib.mdDoc "File used to store the Buildbot Worker password";
       };
 
       hostMessage = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = "Description of this worker";
+        description = lib.mdDoc "Description of this worker";
       };
 
       adminMessage = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = "Name of the administrator of this worker";
+        description = lib.mdDoc "Name of the administrator of this worker";
       };
 
       masterUrl = mkOption {
         default = "localhost:9989";
         type = types.str;
-        description = "Specifies the Buildbot Worker connection string.";
+        description = lib.mdDoc "Specifies the Buildbot Worker connection string.";
       };
 
       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;
         defaultText = literalExpression "pkgs.python3Packages.buildbot-worker";
-        description = "Package to use for buildbot worker.";
+        description = lib.mdDoc "Package to use for buildbot worker.";
         example = literalExpression "pkgs.python2Packages.buildbot-worker";
       };
 
@@ -139,7 +139,7 @@ in {
         default = with pkgs; [ git ];
         defaultText = literalExpression "[ pkgs.git ]";
         type = types.listOf types.package;
-        description = "Packages to add to PATH for the buildbot process.";
+        description = lib.mdDoc "Packages to add to PATH for the buildbot process.";
       };
     };
   };
diff --git a/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixos/modules/services/continuous-integration/buildkite-agents.nix
index 1872567c9f12..7c8f77580ff6 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agents.nix
+++ b/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; });
   };
@@ -34,32 +34,32 @@ let
       enable = mkOption {
         default = true;
         type = types.bool;
-        description = "Whether to enable this buildkite agent";
+        description = lib.mdDoc "Whether to enable this buildkite agent";
       };
 
       package = mkOption {
         default = pkgs.buildkite-agent;
         defaultText = literalExpression "pkgs.buildkite-agent";
-        description = "Which buildkite-agent derivation to use";
+        description = lib.mdDoc "Which buildkite-agent derivation to use";
         type = types.package;
       };
 
       dataDir = mkOption {
         default = "/var/lib/buildkite-agent-${name}";
-        description = "The workdir for the agent";
+        description = lib.mdDoc "The workdir for the agent";
         type = types.str;
       };
 
       runtimePackages = mkOption {
         default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
         defaultText = literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
-        description = "Add programs to the buildkite-agent environment";
+        description = lib.mdDoc "Add programs to the buildkite-agent environment";
         type = types.listOf types.package;
       };
 
       tokenPath = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The token from your Buildkite "Agents" page.
 
           A run-time path to the token file, which is supposed to be provisioned
@@ -70,7 +70,7 @@ let
       name = mkOption {
         type = types.str;
         default = "%hostname-${name}-%n";
-        description = ''
+        description = lib.mdDoc ''
           The name of the agent as seen in the buildkite dashboard.
         '';
       };
@@ -79,7 +79,7 @@ let
         type = types.attrsOf (types.either types.str (types.listOf types.str));
         default = {};
         example = { queue = "default"; docker = "true"; ruby2 ="true"; };
-        description = ''
+        description = lib.mdDoc ''
           Tags for the agent.
         '';
       };
@@ -88,7 +88,7 @@ let
         type = types.lines;
         default = "";
         example = "debug=true";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the configuration file.
         '';
       };
@@ -100,7 +100,7 @@ let
         ## don't end up in the Nix store.
         apply = final: if final == null then null else toString final;
 
-        description = ''
+        description = lib.mdDoc ''
           OpenSSH private key
 
           A run-time path to the key file, which is supposed to be provisioned
@@ -168,10 +168,10 @@ let
       hooksPath = mkOption {
         type = types.path;
         default = hooksDir config;
-        defaultText = literalDocBook "generated from <option>services.buildkite-agents.&lt;name&gt;.hooks</option>";
-        description = ''
+        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.&lt;name&gt;.hooks.&lt;name&gt;</option>
+          Consider using {option}`services.buildkite-agents.<name>.hooks.<name>`
           instead.
         '';
       };
@@ -180,7 +180,7 @@ let
         type = types.str;
         default = "${pkgs.bash}/bin/bash -e -c";
         defaultText = literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
-        description = ''
+        description = lib.mdDoc ''
           Command that buildkite-agent 3 will execute when it spawns a shell.
         '';
       };
@@ -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/nixos/modules/services/continuous-integration/github-runner.nix b/nixos/modules/services/continuous-integration/github-runner.nix
index 30dd919b81a3..24d02c931a4a 100644
--- a/nixos/modules/services/continuous-integration/github-runner.nix
+++ b/nixos/modules/services/continuous-integration/github-runner.nix
@@ -1,320 +1,23 @@
-{ 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 = ''
-        Whether to enable GitHub Actions runner.
-
-        Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
-        <link xlink:href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners"
-        >About self-hosted runners</link>.
-      '';
-      type = lib.types.bool;
-    };
-
-    url = mkOption {
-      type = types.str;
-      description = ''
-        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
-        <literal>https://github.com/nixos</literal>, not like this
-        <literal>https://github.com/nixos/nixpkgs</literal>.
-        Otherwise, you are going to get a <literal>404 NotFound</literal>
-        from <literal>POST https://api.github.com/actions/runner-registration</literal>
-        in the configure script.
-      '';
-      example = "https://github.com/nixos/nixpkgs";
-    };
-
-    tokenFile = mkOption {
-      type = types.path;
-      description = ''
-        The full path to a file which contains the runner registration token.
-        The file should contain exactly one line with the token without any newline.
-        The token can be used to re-register a runner of the same name but is time-limited.
-
-        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 = ''
-        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 = ''
-        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 = ''
-        Extra labels in addition to the default (<literal>["self-hosted", "Linux", "X64"]</literal>).
-
-        Changing this option triggers a new runner registration.
-      '';
-      example = literalExpression ''[ "nixos" ]'';
-      default = [ ];
-    };
-
-    replace = mkOption {
-      type = types.bool;
-      description = ''
-        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 = ''
-        Extra packages to add to <literal>PATH</literal> of the service to make them available to workflows.
-      '';
-      default = [ ];
-    };
-
-    package = mkOption {
-      type = types.package;
-      description = ''
-        Which github-runner derivation to use.
-      '';
-      default = pkgs.github-runner;
-      defaultText = literalExpression "pkgs.github-runner";
-    };
-  };
+{
+  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 = runtimeDir;
-      };
-
-      path = (with pkgs; [
-        bash
-        coreutils
-        git
-        gnutar
-        gzip
-      ]) ++ [
-        config.nix.package
-      ] ++ cfg.extraPackages;
-
-      serviceConfig = rec {
-        ExecStart = "${cfg.package}/bin/runsvc.sh";
-
-        # 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" ] cfg;
-            newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
-            newConfigTokenFilename = ".new-token";
-            runnerCredFiles = [
-              ".credentials"
-              ".credentials_rsaparams"
-              ".runner"
-            ];
-            unconfigureRunner = writeScript "unconfigure" ''
-              differs=
-              # Set `differs = 1` if current and new runner config differ or if `currentConfigPath` does not exist
-              ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 || differs=1
-              # Also trigger a registration if the token content changed
-              ${pkgs.diffutils}/bin/diff -q \
-                "$STATE_DIRECTORY"/${currentConfigTokenFilename} \
-                ${escapeShellArg cfg.tokenFile} \
-                >/dev/null 2>&1 || differs=1
-
-              if [[ -n "$differs" ]]; 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."
-                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"
-
-                token=$(< "$STATE_DIRECTORY"/${newConfigTokenFilename})
-                RUNNER_ROOT="$STATE_DIRECTORY" ${cfg.package}/bin/config.sh \
-                  --unattended \
-                  --disableupdate \
-                  --work "$RUNTIME_DIRECTORY" \
-                  --url ${escapeShellArg cfg.url} \
-                  --token "$token" \
-                  --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)} \
-                  --name ${escapeShellArg cfg.name} \
-                  ${optionalString cfg.replace "--replace"} \
-                  ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}
-
-                # 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
-          ];
-
-        # 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;
-
-        KillMode = "process";
-        KillSignal = "SIGTERM";
-
-        # 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 = "";
-        LockPersonality = true;
-        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";
-        ProcSubset = "pid";
-        SystemCallFilter = [
-          "~@debug"
-          "~@mount"
-          "~@privileged"
-          "~@cpu-emulation"
-          "~@obsolete"
-        ];
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
-
-        # Needs network access
-        PrivateNetwork = false;
-        # Cannot be true due to Node
-        MemoryDenyWriteExecute = false;
-      };
-    };
+    services.github-runners.${cfg.name} = cfg;
   };
 }
diff --git a/nixos/modules/services/continuous-integration/github-runner/options.nix b/nixos/modules/services/continuous-integration/github-runner/options.nix
new file mode 100644
index 000000000000..72ac0c129900
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/github-runner/options.nix
@@ -0,0 +1,173 @@
+{ 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 runner registration token or a
+      (fine-grained) 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. Fine-grained PATs need read and write permission
+      to the "Administration" resources.
+
+      Changing this option or the file's content triggers a new runner registration.
+    '';
+    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 ''
+      Overrides for the systemd service. Can be used to adjust the sandboxing options.
+    '';
+    example = {
+      ProtectHome = false;
+    };
+    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";
+  };
+}
diff --git a/nixos/modules/services/continuous-integration/github-runner/service.nix b/nixos/modules/services/continuous-integration/github-runner/service.nix
new file mode 100644
index 000000000000..cd81631582f9
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/github-runner/service.nix
@@ -0,0 +1,257 @@
+{ 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;
+
+{
+  description = "GitHub Actions runner";
+
+  wantedBy = [ "multi-user.target" ];
+  wants = [ "network-online.target" ];
+  after = [ "network.target" "network-online.target" ];
+
+  environment = {
+    HOME = runtimeDir;
+    RUNNER_ROOT = stateDir;
+  } // cfg.extraEnvironment;
+
+  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}
+        '';
+        runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" ] 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        '';
+        configureRunner = writeScript "configure" ''
+          if [[ -e "${newConfigTokenPath}" ]]; 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_" 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/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 "${newConfigTokenPath}"
+            # 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";
+    # 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 = runtimeDir;
+
+    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 = "";
+    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;
+
+    # Note that this has some interactions with the User setting; so you may
+    # want to consult the systemd docs if using both.
+    DynamicUser = true;
+  } // (
+    lib.optionalAttrs (cfg.user != null) { User = cfg.user; }
+  ) // cfg.serviceOverrides;
+}
diff --git a/nixos/modules/services/continuous-integration/github-runners.nix b/nixos/modules/services/continuous-integration/github-runners.nix
new file mode 100644
index 000000000000..78b57f9c7a25
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/github-runners.nix
@@ -0,0 +1,56 @@
+{ 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}";
+        }))
+    );
+  };
+}
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index dc58c6345239..7b1c4da86260 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -22,6 +22,14 @@ let
       export CONFIG_FILE=${configPath}
 
       mkdir -p $(dirname ${configPath})
+      touch ${configPath}
+
+      # update global options
+      remarshal --if toml --of json ${configPath} \
+        | jq -cM 'with_entries(select([.key] | inside(["runners"])))' \
+        | jq -scM '.[0] + .[1]' - <(echo ${escapeShellArg (toJSON cfg.settings)}) \
+        | remarshal --if json --of toml \
+        | sponge ${configPath}
 
       # remove no longer existing services
       gitlab-runner verify --delete
@@ -36,12 +44,12 @@ let
 
       # register new services
       ${concatStringsSep "\n" (mapAttrsToList (name: service: ''
-        if echo "$NEW_SERVICES" | grep -xq ${name}; then
+        if echo "$NEW_SERVICES" | grep -xq "${name}"; then
           bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
             "set -a && source ${service.registrationConfigFile} &&"
             "gitlab-runner register"
             "--non-interactive"
-            "--name ${name}"
+            (if service.description != null then "--description \"${service.description}\"" else "--name '${name}'")
             "--executor ${service.executor}"
             "--limit ${toString service.limit}"
             "--request-concurrency ${toString service.requestConcurrency}"
@@ -91,22 +99,6 @@ let
           --name "$NAME" && sleep 1
       done
 
-      # update global options
-      remarshal --if toml --of json ${configPath} \
-        | jq -cM ${escapeShellArg (concatStringsSep " | " [
-            ".check_interval = ${toJSON cfg.checkInterval}"
-            ".concurrent = ${toJSON cfg.concurrent}"
-            ".sentry_dsn = ${toJSON cfg.sentryDSN}"
-            ".listen_address = ${toJSON cfg.prometheusListenAddress}"
-            ".session_server.listen_address = ${toJSON cfg.sessionServer.listenAddress}"
-            ".session_server.advertise_address = ${toJSON cfg.sessionServer.advertiseAddress}"
-            ".session_server.session_timeout = ${toJSON cfg.sessionServer.sessionTimeout}"
-            "del(.[] | nulls)"
-            "del(.session_server[] | nulls)"
-          ])} \
-        | remarshal --if json --of toml \
-        | sponge ${configPath}
-
       # make config file readable by service
       chown -R --reference=$HOME $(dirname ${configPath})
     '');
@@ -117,109 +109,39 @@ let
 in
 {
   options.services.gitlab-runner = {
-    enable = mkEnableOption "Gitlab Runner";
+    enable = mkEnableOption (lib.mdDoc "Gitlab Runner");
     configFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Configuration file for gitlab-runner.
 
-        <option>configFile</option> takes precedence over <option>services</option>.
-        <option>checkInterval</option> and <option>concurrent</option> will be ignored too.
+        {option}`configFile` takes precedence over {option}`services`.
+        {option}`checkInterval` and {option}`concurrent` will be ignored too.
 
-        This option is deprecated, please use <option>services</option> instead.
-        You can use <option>registrationConfigFile</option> and
-        <option>registrationFlags</option>
+        This option is deprecated, please use {option}`services` instead.
+        You can use {option}`registrationConfigFile` and
+        {option}`registrationFlags`
         for settings not covered by this module.
       '';
     };
-    checkInterval = mkOption {
-      type = types.int;
-      default = 0;
-      example = literalExpression "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3";
-      description = ''
-        Defines the interval length, in seconds, between new jobs check.
-        The default value is 3;
-        if set to 0 or lower, the default value will be used.
-        See <link xlink:href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#how-check_interval-works">runner documentation</link> for more information.
-      '';
-    };
-    concurrent = mkOption {
-      type = types.int;
-      default = 1;
-      example = literalExpression "config.nix.settings.max-jobs";
-      description = ''
-        Limits how many jobs globally can be run concurrently.
-        The most upper limit of jobs using all defined runners.
-        0 does not mean unlimited.
-      '';
-    };
-    sentryDSN = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "https://public:private@host:port/1";
-      description = ''
-        Data Source Name for tracking of all system level errors to Sentry.
-      '';
-    };
-    prometheusListenAddress = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "localhost:8080";
-      description = ''
-        Address (&lt;host&gt;:&lt;port&gt;) on which the Prometheus metrics HTTP server
-        should be listening.
-      '';
-    };
-    sessionServer = mkOption {
+    settings = mkOption {
       type = types.submodule {
-        options = {
-          listenAddress = mkOption {
-            type = types.nullOr types.str;
-            default = null;
-            example = "0.0.0.0:8093";
-            description = ''
-              An internal URL to be used for the session server.
-            '';
-          };
-          advertiseAddress = mkOption {
-            type = types.nullOr types.str;
-            default = null;
-            example = "runner-host-name.tld:8093";
-            description = ''
-              The URL that the Runner will expose to GitLab to be used
-              to access the session server.
-              Fallbacks to <option>listenAddress</option> if not defined.
-            '';
-          };
-          sessionTimeout = mkOption {
-            type = types.int;
-            default = 1800;
-            description = ''
-              How long in seconds the session can stay active after
-              the job completes (which will block the job from finishing).
-            '';
-          };
-        };
+        freeformType = (pkgs.formats.json { }).type;
       };
       default = { };
-      example = literalExpression ''
-        {
-          listenAddress = "0.0.0.0:8093";
-        }
-      '';
-      description = ''
-        The session server allows the user to interact with jobs
-        that the Runner is responsible for. A good example of this is the
-        <link xlink:href="https://docs.gitlab.com/ee/ci/interactive_web_terminal/index.html">interactive web terminal</link>.
+      description = lib.mdDoc ''
+        Global gitlab-runner configuration. See
+        <https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section>
+        for supported values.
       '';
     };
     gracefulTermination = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      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.
       '';
     };
@@ -227,7 +149,7 @@ in
       type = types.str;
       default = "infinity";
       example = "5min 20s";
-      description = ''
+      description = lib.mdDoc ''
         Time to wait until a graceful shutdown is turned into a forceful one.
       '';
     };
@@ -236,17 +158,17 @@ in
       default = pkgs.gitlab-runner;
       defaultText = literalExpression "pkgs.gitlab-runner";
       example = literalExpression "pkgs.gitlab-runner_1_11";
-      description = "Gitlab Runner package to use.";
+      description = lib.mdDoc "Gitlab Runner package to use.";
     };
     extraPackages = mkOption {
       type = types.listOf types.package;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         Extra packages to add to PATH for the gitlab-runner process.
       '';
     };
     services = mkOption {
-      description = "GitLab Runner services.";
+      description = lib.mdDoc "GitLab Runner services.";
       default = { };
       example = literalExpression ''
         {
@@ -328,17 +250,17 @@ in
         options = {
           registrationConfigFile = mkOption {
             type = types.path;
-            description = ''
+            description = lib.mdDoc ''
               Absolute path to a file with environment variables
               used for gitlab-runner registration.
               A list of all supported environment variables can be found in
-              <literal>gitlab-runner register --help</literal>.
+              `gitlab-runner register --help`.
 
               Ones that you probably want to set is
 
-              <literal>CI_SERVER_URL=&lt;CI server URL&gt;</literal>
+              `CI_SERVER_URL=<CI server URL>`
 
-              <literal>REGISTRATION_TOKEN=&lt;registration secret&gt;</literal>
+              `REGISTRATION_TOKEN=<registration secret>`
 
               WARNING: make sure to use quoted absolute path,
               or it is going to be copied to Nix Store.
@@ -348,10 +270,10 @@ in
             type = types.listOf types.str;
             default = [ ];
             example = [ "--docker-helper-image my/gitlab-runner-helper" ];
-            description = ''
+            description = lib.mdDoc ''
               Extra command-line flags passed to
-              <literal>gitlab-runner register</literal>.
-              Execute <literal>gitlab-runner register --help</literal>
+              `gitlab-runner register`.
+              Execute `gitlab-runner register --help`
               for a list of supported flags.
             '';
           };
@@ -359,25 +281,32 @@ in
             type = types.attrsOf types.str;
             default = { };
             example = { NAME = "value"; };
-            description = ''
+            description = lib.mdDoc ''
               Custom environment variables injected to build environment.
-              For secrets you can use <option>registrationConfigFile</option>
-              with <literal>RUNNER_ENV</literal> variable set.
+              For secrets you can use {option}`registrationConfigFile`
+              with `RUNNER_ENV` variable set.
+            '';
+          };
+          description = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = lib.mdDoc ''
+              Name/description of the runner.
             '';
           };
           executor = mkOption {
             type = types.str;
             default = "docker";
-            description = ''
+            description = lib.mdDoc ''
               Select executor, eg. shell, docker, etc.
-              See <link xlink:href="https://docs.gitlab.com/runner/executors/README.html">runner documentation</link> for more information.
+              See [runner documentation](https://docs.gitlab.com/runner/executors/README.html) for more information.
             '';
           };
           buildsDir = mkOption {
             type = types.nullOr types.path;
             default = null;
             example = "/var/lib/gitlab-runner/builds";
-            description = ''
+            description = lib.mdDoc ''
               Absolute path to a directory where builds will be stored
               in context of selected executor (Locally, Docker, SSH).
             '';
@@ -386,14 +315,14 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "http://gitlab.example.local";
-            description = ''
+            description = lib.mdDoc ''
               Overwrite the URL for the GitLab instance. Used if the Runner can’t connect to GitLab on the URL GitLab exposes itself.
             '';
           };
           dockerImage = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Docker image to be used.
             '';
           };
@@ -401,7 +330,7 @@ in
             type = types.listOf types.str;
             default = [ ];
             example = [ "/var/run/docker.sock:/var/run/docker.sock" ];
-            description = ''
+            description = lib.mdDoc ''
               Bind-mount a volume and create it
               if it doesn't exist prior to mounting.
             '';
@@ -409,14 +338,14 @@ in
           dockerDisableCache = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Disable all container caching.
             '';
           };
           dockerPrivileged = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Give extended privileges to container.
             '';
           };
@@ -424,7 +353,7 @@ in
             type = types.listOf types.str;
             default = [ ];
             example = [ "other-host:127.0.0.1" ];
-            description = ''
+            description = lib.mdDoc ''
               Add a custom host-to-IP mapping.
             '';
           };
@@ -432,7 +361,7 @@ in
             type = types.listOf types.str;
             default = [ ];
             example = [ "ruby:*" "python:*" "php:*" "my.registry.tld:5000/*:*" ];
-            description = ''
+            description = lib.mdDoc ''
               Whitelist allowed images.
             '';
           };
@@ -440,21 +369,21 @@ in
             type = types.listOf types.str;
             default = [ ];
             example = [ "postgres:9" "redis:*" "mysql:*" ];
-            description = ''
+            description = lib.mdDoc ''
               Whitelist allowed services.
             '';
           };
           preCloneScript = mkOption {
             type = types.nullOr types.path;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Runner-specific command script executed before code is pulled.
             '';
           };
           preBuildScript = mkOption {
             type = types.nullOr types.path;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Runner-specific command script executed after code is pulled,
               just before build executes.
             '';
@@ -462,7 +391,7 @@ in
           postBuildScript = mkOption {
             type = types.nullOr types.path;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Runner-specific command script executed after code is pulled
               and just after build executes.
             '';
@@ -470,22 +399,22 @@ in
           tagList = mkOption {
             type = types.listOf types.str;
             default = [ ];
-            description = ''
+            description = lib.mdDoc ''
               Tag list.
             '';
           };
           runUntagged = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Register to run untagged builds; defaults to
-              <literal>true</literal> when <option>tagList</option> is empty.
+              `true` when {option}`tagList` is empty.
             '';
           };
           limit = mkOption {
             type = types.int;
             default = 0;
-            description = ''
+            description = lib.mdDoc ''
               Limit how many jobs can be handled concurrently by this service.
               0 (default) simply means don't limit.
             '';
@@ -493,14 +422,14 @@ in
           requestConcurrency = mkOption {
             type = types.int;
             default = 0;
-            description = ''
+            description = lib.mdDoc ''
               Limit number of concurrent requests for new jobs from GitLab.
             '';
           };
           maximumTimeout = mkOption {
             type = types.int;
             default = 0;
-            description = ''
+            description = lib.mdDoc ''
               What is the maximum timeout (in seconds) that will be set for
               job when using this Runner. 0 (default) simply means don't limit.
             '';
@@ -508,7 +437,7 @@ in
           protected = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               When set to true Runner will only run on pipelines
               triggered on protected branches.
             '';
@@ -516,20 +445,57 @@ in
           debugTraceDisabled = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               When set to true Runner will disable the possibility of
-              using the <literal>CI_DEBUG_TRACE</literal> feature.
+              using the `CI_DEBUG_TRACE` feature.
             '';
           };
         };
       });
     };
+    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
       (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))
-    ++ optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`.";
+      (filterAttrs (n: v: isStorePath v.registrationConfigFile) cfg.services));
+
     environment.systemPackages = [ cfg.package ];
     systemd.services.gitlab-runner = {
       description = "Gitlab Runner";
@@ -568,6 +534,22 @@ in
         KillMode = "process";
       };
     };
+    # Enable periodic clear-docker-cache script
+    systemd.services.gitlab-runner-clear-docker-cache = {
+      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 = optional cfg.clear-docker-cache.enable 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)
@@ -577,5 +559,14 @@ in
     (mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] )
     (mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" )
     (mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" )
+
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "checkInterval" ] [ "services" "gitlab-runner" "settings" "check_interval" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "concurrent" ] [ "services" "gitlab-runner" "settings" "concurrent" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sentryDSN" ] [ "services" "gitlab-runner" "settings" "sentry_dsn" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "prometheusListenAddress" ] [ "services" "gitlab-runner" "settings" "listen_address" ] )
+
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "listenAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "listen_address" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "advertiseAddress" ] [ "services" "gitlab-runner" "settings" "session_server" "advertise_address" ] )
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "sessionServer" "sessionTimeout" ] [ "services" "gitlab-runner" "settings" "session_server" "session_timeout" ] )
   ];
 }
diff --git a/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
index c63998c6736a..c0d752443a16 100644
--- a/nixos/modules/services/continuous-integration/gocd-agent/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -8,12 +8,12 @@ let
 in {
   options = {
     services.gocd-agent = {
-      enable = mkEnableOption "gocd-agent";
+      enable = mkEnableOption (lib.mdDoc "gocd-agent");
 
       user = mkOption {
         default = "gocd-agent";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User the Go.CD agent should execute under.
         '';
       };
@@ -21,7 +21,7 @@ in {
       group = mkOption {
         default = "gocd-agent";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           If the default user "gocd-agent" is configured then this is the primary
           group of that user.
         '';
@@ -31,7 +31,7 @@ in {
         type = types.listOf types.str;
         default = [ ];
         example = [ "wheel" "docker" ];
-        description = ''
+        description = lib.mdDoc ''
           List of extra groups that the "gocd-agent" user should be a part of.
         '';
       };
@@ -40,7 +40,7 @@ in {
         default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
         defaultText = literalExpression "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
-        description = ''
+        description = lib.mdDoc ''
           Packages to add to PATH for the Go.CD agent process.
         '';
       };
@@ -53,7 +53,7 @@ in {
           agent.auto.register.environments=QA,Performance
           agent.auto.register.hostname=Agent01
         '';
-        description = ''
+        description = lib.mdDoc ''
           Agent registration configuration.
         '';
       };
@@ -61,7 +61,7 @@ in {
       goServer = mkOption {
         default = "https://127.0.0.1:8154/go";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           URL of the GoCD Server to attach the Go.CD Agent to.
         '';
       };
@@ -69,7 +69,7 @@ in {
       workDir = mkOption {
         default = "/var/lib/go-agent";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the working directory in which the Go.CD agent java archive resides.
         '';
       };
@@ -77,7 +77,7 @@ in {
       initialJavaHeapSize = mkOption {
         default = "128m";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the initial java heap memory size for the Go.CD agent java process.
         '';
       };
@@ -85,7 +85,7 @@ in {
       maxJavaHeapMemory = mkOption {
         default = "256m";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the java maximum heap memory size for the Go.CD agent java process.
         '';
       };
@@ -108,7 +108,7 @@ in {
             "-Djava.security.egd=file:/dev/./urandom"
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Specifies startup command line arguments to pass to Go.CD agent
           java process.
         '';
@@ -127,7 +127,7 @@ in {
           "-XX:+PrintGCDetails"
           "-XX:+PrintGC"
         ];
-        description = ''
+        description = lib.mdDoc ''
           Specifies additional command line arguments to pass to Go.CD agent
           java process.  Example contains debug and gcLog arguments.
         '';
@@ -136,10 +136,10 @@ in {
       environment = mkOption {
         default = { };
         type = with types; attrsOf str;
-        description = ''
+        description = lib.mdDoc ''
           Additional environment variables to be passed to the Go.CD agent process.
           As a base environment, Go.CD agent receives NIX_PATH from
-          <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+          {option}`environment.sessionVariables`, NIX_REMOTE is set to
           "daemon".
         '';
       };
diff --git a/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixos/modules/services/continuous-integration/gocd-server/default.nix
index 3540656f9344..bf7fd529bfca 100644
--- a/nixos/modules/services/continuous-integration/gocd-server/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -8,12 +8,12 @@ let
 in {
   options = {
     services.gocd-server = {
-      enable = mkEnableOption "gocd-server";
+      enable = mkEnableOption (lib.mdDoc "gocd-server");
 
       user = mkOption {
         default = "gocd-server";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User the Go.CD server should execute under.
         '';
       };
@@ -21,7 +21,7 @@ in {
       group = mkOption {
         default = "gocd-server";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           If the default user "gocd-server" is configured then this is the primary group of that user.
         '';
       };
@@ -30,7 +30,7 @@ in {
         default = [ ];
         type = types.listOf types.str;
         example = [ "wheel" "docker" ];
-        description = ''
+        description = lib.mdDoc ''
           List of extra groups that the "gocd-server" user should be a part of.
         '';
       };
@@ -39,15 +39,15 @@ in {
         default = "0.0.0.0";
         example = "localhost";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the bind address on which the Go.CD server HTTP interface listens.
         '';
       };
 
       port = mkOption {
         default = 8153;
-        type = types.int;
-        description = ''
+        type = types.port;
+        description = lib.mdDoc ''
           Specifies port number on which the Go.CD server HTTP interface listens.
         '';
       };
@@ -55,7 +55,7 @@ in {
       sslPort = mkOption {
         default = 8154;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Specifies port number on which the Go.CD server HTTPS interface listens.
         '';
       };
@@ -63,7 +63,7 @@ in {
       workDir = mkOption {
         default = "/var/lib/go-server";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the working directory in which the Go.CD server java archive resides.
         '';
       };
@@ -72,7 +72,7 @@ in {
         default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
         defaultText = literalExpression "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
         type = types.listOf types.package;
-        description = ''
+        description = lib.mdDoc ''
           Packages to add to PATH for the Go.CD server's process.
         '';
       };
@@ -80,7 +80,7 @@ in {
       initialJavaHeapSize = mkOption {
         default = "512m";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the initial java heap memory size for the Go.CD server's java process.
         '';
       };
@@ -88,7 +88,7 @@ in {
       maxJavaHeapMemory = mkOption {
         default = "1024m";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the java maximum heap memory size for the Go.CD server's java process.
         '';
       };
@@ -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,10 +121,12 @@ 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"
           ]
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Specifies startup command line arguments to pass to Go.CD server
           java process.
         '';
@@ -141,7 +145,7 @@ in {
           "-XX:+PrintGCDetails"
           "-XX:+PrintGC"
         ];
-        description = ''
+        description = lib.mdDoc ''
           Specifies additional command line arguments to pass to Go.CD server's
           java process.  Example contains debug and gcLog arguments.
         '';
@@ -150,10 +154,10 @@ in {
       environment = mkOption {
         default = { };
         type = with types; attrsOf str;
-        description = ''
+        description = lib.mdDoc ''
           Additional environment variables to be passed to the gocd-server process.
           As a base environment, gocd-server receives NIX_PATH from
-          <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+          {option}`environment.sessionVariables`, NIX_REMOTE is set to
           "daemon".
         '';
       };
@@ -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/nixos/modules/services/continuous-integration/hail.nix b/nixos/modules/services/continuous-integration/hail.nix
index 4070a3425c4f..62e8b8077c07 100644
--- a/nixos/modules/services/continuous-integration/hail.nix
+++ b/nixos/modules/services/continuous-integration/hail.nix
@@ -13,30 +13,30 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      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;
       default = "hail-profile";
-      description = "The name of the Nix profile used by Hail.";
+      description = lib.mdDoc "The name of the Nix profile used by Hail.";
     };
     hydraJobUri = mkOption {
       type = types.str;
-      description = "The URI of the Hydra Job.";
+      description = lib.mdDoc "The URI of the Hydra Job.";
     };
     netrc = mkOption {
       type = types.nullOr types.path;
-      description = "The netrc file to use when fetching data from Hydra.";
+      description = lib.mdDoc "The netrc file to use when fetching data from Hydra.";
       default = null;
     };
     package = mkOption {
       type = types.package;
       default = pkgs.haskellPackages.hail;
       defaultText = literalExpression "pkgs.haskellPackages.hail";
-      description = "Hail package to use.";
+      description = lib.mdDoc "Hail package to use.";
     };
   };
 
diff --git a/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix b/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
index 80c88714bfc1..663f3df775c3 100644
--- a/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
+++ b/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
@@ -10,7 +10,7 @@
 let
   inherit (lib)
     filterAttrs
-    literalDocBook
+    literalMD
     literalExpression
     mkIf
     mkOption
@@ -28,7 +28,7 @@ let
     freeformType = format.type;
     options = {
       apiBaseUrl = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           API base URL that the agent will connect to.
 
           When using Hercules CI Enterprise, set this to the URL where your
@@ -40,19 +40,19 @@ let
       baseDirectory = mkOption {
         type = types.path;
         default = "/var/lib/hercules-ci-agent";
-        description = ''
+        description = lib.mdDoc ''
           State directory (secrets, work directory, etc) for agent
         '';
       };
       concurrentTasks = mkOption {
-        description = ''
+        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 <literal>x86_64-linux</literal>
+          At minimum, you need 2 concurrent tasks for `x86_64-linux`
           in your cluster, to allow for import from derivation.
 
-          <literal>concurrentTasks</literal> can be around the CPU core count or lower if memory is
+          `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,
@@ -66,7 +66,7 @@ let
         default = "auto";
       };
       labels = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           A key-value map of user data.
 
           This data will be available to organization members in the dashboard and API.
@@ -85,7 +85,7 @@ let
         '';
       };
       workDirectory = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The directory in which temporary subdirectories are created for task state. This includes sources for Nix evaluation.
         '';
         type = types.path;
@@ -93,25 +93,25 @@ let
         defaultText = literalExpression ''baseDirectory + "/work"'';
       };
       staticSecretsDirectory = mkOption {
-        description = ''
-          This is the default directory to look for statically configured secrets like <literal>cluster-join-token.key</literal>.
+        description = lib.mdDoc ''
+          This is the default directory to look for statically configured secrets like `cluster-join-token.key`.
 
-          See also <literal>clusterJoinTokenPath</literal> and <literal>binaryCachesPath</literal> for fine-grained configuration.
+          See also `clusterJoinTokenPath` and `binaryCachesPath` for fine-grained configuration.
         '';
         type = types.path;
         default = config.baseDirectory + "/secrets";
         defaultText = literalExpression ''baseDirectory + "/secrets"'';
       };
       clusterJoinTokenPath = mkOption {
-        description = ''
+        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
-          <link xlink:href="https://hercules-ci.com/dashboard">https://hercules-ci.com/dashboard</link>.
+          <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
-          <literal>deployment.keys</literal>, or manual installation.
+          `deployment.keys`, or manual installation.
 
           The contents of the file are used for authentication between the agent and the API.
         '';
@@ -120,29 +120,28 @@ let
         defaultText = literalExpression ''staticSecretsDirectory + "/cluster-join-token.key"'';
       };
       binaryCachesPath = mkOption {
-        description = ''
+        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
-          <literal>deployment.keys</literal>, or manual installation.
+          `deployment.keys`, 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>.
+          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 = ''
+        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
-          <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>.
+          `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";
@@ -190,26 +189,26 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable to run Hercules CI Agent as a system service.
 
-        <link xlink:href="https://hercules-ci.com">Hercules CI</link> is a
+        [Hercules CI](https://hercules-ci.com) is a
         continuous integation service that is centered around Nix.
 
-        Support is available at <link xlink:href="mailto:help@hercules-ci.com">help@hercules-ci.com</link>.
+        Support is available at [help@hercules-ci.com](mailto:help@hercules-ci.com).
       '';
     };
     checkNix = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      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 = ''
+      description = lib.mdDoc ''
         Package containing the bin/hercules-ci-agent executable.
       '';
       type = types.package;
@@ -217,12 +216,12 @@ in
       defaultText = literalExpression "pkgs.hercules-ci-agent";
     };
     settings = mkOption {
-      description = ''
-        These settings are written to the <literal>agent.toml</literal> file.
+      description = lib.mdDoc ''
+        These settings are written to the `agent.toml` file.
 
         Not all settings are listed as options, can be set nonetheless.
 
-        For the exhaustive list of settings, see <link xlink:href="https://docs.hercules-ci.com/hercules-ci/reference/agent-config/"/>.
+        For the exhaustive list of settings, see <https://docs.hercules-ci.com/hercules-ci/reference/agent-config/>.
       '';
       type = types.submoduleWith { modules = [ settingsModule ]; };
     };
@@ -236,8 +235,8 @@ in
     tomlFile = mkOption {
       type = types.path;
       internal = true;
-      defaultText = literalDocBook "generated <literal>hercules-ci-agent.toml</literal>";
-      description = ''
+      defaultText = literalMD "generated `hercules-ci-agent.toml`";
+      description = lib.mdDoc ''
         The fully assembled config file.
       '';
     };
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index cc5de97d6d10..564bcd37dec5 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/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 = ''
@@ -78,7 +78,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run Hydra services.
         '';
       };
@@ -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
@@ -99,14 +99,14 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.hydra-unstable;
-        defaultText = literalExpression "pkgs.hydra-unstable";
-        description = "The Hydra package.";
+        default = pkgs.hydra_unstable;
+        defaultText = literalExpression "pkgs.hydra_unstable";
+        description = lib.mdDoc "The Hydra package.";
       };
 
       hydraURL = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The base URL for the Hydra webserver instance. Used for links in emails.
         '';
       };
@@ -115,16 +115,16 @@ 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 = ''
+        description = lib.mdDoc ''
           TCP port the web server should listen to.
         '';
       };
@@ -132,7 +132,7 @@ in
       minimumDiskFree = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Threshold of minimum disk space (GiB) to determine if the queue runner should run or not.
         '';
       };
@@ -140,14 +140,14 @@ in
       minimumDiskFreeEvaluator = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Threshold of minimum disk space (GiB) to determine if the evaluator should run or not.
         '';
       };
 
       notificationSender = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Sender email address used for email notifications.
         '';
       };
@@ -156,7 +156,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           Hostname of the SMTP server to use to send email.
         '';
       };
@@ -164,7 +164,7 @@ in
       tracker = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Piece of HTML that is included on all pages.
         '';
       };
@@ -172,7 +172,7 @@ in
       logo = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to a file containing the logo of your Hydra instance.
         '';
       };
@@ -180,24 +180,24 @@ in
       debugServer = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to run the server in debug mode.";
+        description = lib.mdDoc "Whether to run the server in debug mode.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
-        description = "Extra lines for the Hydra configuration.";
+        description = lib.mdDoc "Extra lines for the Hydra configuration.";
       };
 
       extraEnv = mkOption {
         type = types.attrsOf types.str;
         default = {};
-        description = "Extra environment variables for Hydra.";
+        description = lib.mdDoc "Extra environment variables for Hydra.";
       };
 
       gcRootsDir = mkOption {
         type = types.path;
         default = "/nix/var/nix/gcroots/hydra";
-        description = "Directory that holds Hydra garbage collector roots.";
+        description = lib.mdDoc "Directory that holds Hydra garbage collector roots.";
       };
 
       buildMachinesFiles = mkOption {
@@ -205,13 +205,13 @@ in
         default = optional (config.nix.buildMachines != []) "/etc/nix/machines";
         defaultText = literalExpression ''optional (config.nix.buildMachines != []) "/etc/nix/machines"'';
         example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ];
-        description = "List of files containing build machines.";
+        description = lib.mdDoc "List of files containing build machines.";
       };
 
       useSubstitutes = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use binary caches for downloading store paths. Note that
           binary substitutions trigger (a potentially large number of) additional
           HTTP requests that slow down the queue monitor thread significantly.
@@ -298,27 +298,32 @@ in
         environment = env // {
           HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-init";
         };
+        path = [ pkgs.util-linux ];
         preStart = ''
           mkdir -p ${baseDir}
-          chown hydra.hydra ${baseDir}
+          chown hydra:hydra ${baseDir}
           chmod 0750 ${baseDir}
 
           ln -sf ${hydraConf} ${baseDir}/hydra.conf
 
           mkdir -m 0700 -p ${baseDir}/www
-          chown hydra-www.hydra ${baseDir}/www
+          chown hydra-www:hydra ${baseDir}/www
 
           mkdir -m 0700 -p ${baseDir}/queue-runner
           mkdir -m 0750 -p ${baseDir}/build-logs
-          chown hydra-queue-runner.hydra ${baseDir}/queue-runner ${baseDir}/build-logs
+          mkdir -m 0750 -p ${baseDir}/runcommand-logs
+          chown hydra-queue-runner.hydra \
+            ${baseDir}/queue-runner \
+            ${baseDir}/build-logs \
+            ${baseDir}/runcommand-logs
 
           ${optionalString haveLocalDB ''
             if ! [ -e ${baseDir}/.db-created ]; then
-              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser hydra
-              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -O hydra hydra
+              runuser -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser hydra
+              runuser -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -- -O hydra hydra
               touch ${baseDir}/.db-created
             fi
-            echo "create extension if not exists pg_trgm" | ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql hydra
+            echo "create extension if not exists pg_trgm" | runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql hydra
           ''}
 
           if [ ! -e ${cfg.gcRootsDir} ]; then
@@ -338,7 +343,7 @@ in
             rmdir /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots
           fi
 
-          chown hydra.hydra ${cfg.gcRootsDir}
+          chown hydra:hydra ${cfg.gcRootsDir}
           chmod 2775 ${cfg.gcRootsDir}
         '';
         serviceConfig.ExecStart = "${hydra-package}/bin/hydra-init";
diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix
index d37dcb5519d2..a9a587b41e88 100644
--- a/nixos/modules/services/continuous-integration/jenkins/default.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -9,7 +9,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the jenkins continuous integration server.
         '';
       };
@@ -17,7 +17,7 @@ in {
       user = mkOption {
         default = "jenkins";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User the jenkins server should execute under.
         '';
       };
@@ -25,7 +25,7 @@ in {
       group = mkOption {
         default = "jenkins";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           If the default user "jenkins" is configured then this is the primary
           group of that user.
         '';
@@ -35,7 +35,7 @@ in {
         type = types.listOf types.str;
         default = [ ];
         example = [ "wheel" "dialout" ];
-        description = ''
+        description = lib.mdDoc ''
           List of extra groups that the "jenkins" user should be a part of.
         '';
       };
@@ -43,7 +43,7 @@ in {
       home = mkOption {
         default = "/var/lib/jenkins";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path to use as JENKINS_HOME. If the default user "jenkins" is configured then
           this is the home of the "jenkins" user.
         '';
@@ -53,7 +53,7 @@ in {
         default = "0.0.0.0";
         example = "localhost";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the bind address on which the jenkins HTTP interface listens.
           The default is the wildcard address.
         '';
@@ -62,7 +62,7 @@ in {
       port = mkOption {
         default = 8080;
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           Specifies port number on which the jenkins HTTP interface listens.
           The default is 8080.
         '';
@@ -72,7 +72,7 @@ in {
         default = "";
         example = "/jenkins";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specifies a urlPrefix to use with jenkins.
           If the example /jenkins is given, the jenkins server will be
           accessible using localhost:8080/jenkins.
@@ -83,14 +83,14 @@ in {
         default = pkgs.jenkins;
         defaultText = literalExpression "pkgs.jenkins";
         type = types.package;
-        description = "Jenkins package to use.";
+        description = lib.mdDoc "Jenkins package to use.";
       };
 
       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 = ''
+        description = lib.mdDoc ''
           Packages to add to PATH for the jenkins process.
         '';
       };
@@ -98,12 +98,12 @@ in {
       environment = mkOption {
         default = { };
         type = with types; attrsOf str;
-        description = ''
+        description = lib.mdDoc ''
           Additional environment variables to be passed to the jenkins process.
           As a base environment, jenkins receives NIX_PATH from
-          <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+          {option}`environment.sessionVariables`, NIX_REMOTE is set to
           "daemon" and JENKINS_HOME is set to the value of
-          <option>services.jenkins.home</option>.
+          {option}`services.jenkins.home`.
           This option has precedence and can be used to override those
           mentioned variables.
         '';
@@ -112,13 +112,13 @@ in {
       plugins = mkOption {
         default = null;
         type = types.nullOr (types.attrsOf types.package);
-        description = ''
+        description = lib.mdDoc ''
           A set of plugins to activate. Note that this will completely
           remove and replace any previously installed plugins. If you
           have manually-installed plugins that you want to keep while
           using this module, set this option to
-          <literal>null</literal>. You can generate this set with a
-          tool such as <literal>jenkinsPlugins2nix</literal>.
+          `null`. You can generate this set with a
+          tool such as `jenkinsPlugins2nix`.
         '';
         example = literalExpression ''
           import path/to/jenkinsPlugins2nix-generated-plugins.nix { inherit (pkgs) fetchurl stdenv; }
@@ -129,7 +129,7 @@ in {
         type = types.listOf types.str;
         default = [ ];
         example = [ "--debug=9" ];
-        description = ''
+        description = lib.mdDoc ''
           Additional command line arguments to pass to Jenkins.
         '';
       };
@@ -138,7 +138,7 @@ in {
         type = types.listOf types.str;
         default = [ ];
         example = [ "-Xmx80m" ];
-        description = ''
+        description = lib.mdDoc ''
           Additional command line arguments to pass to the Java run time (as opposed to Jenkins).
         '';
       };
@@ -146,12 +146,12 @@ in {
       withCLI = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to make the CLI available.
 
           More info about the CLI available at
-          <link xlink:href="https://www.jenkins.io/doc/book/managing/cli">
-          https://www.jenkins.io/doc/book/managing/cli</link> .
+          [
+          https://www.jenkins.io/doc/book/managing/cli](https://www.jenkins.io/doc/book/managing/cli) .
         '';
       };
     };
@@ -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/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
index 3ca1542c18f2..3a1c6c1a371d 100644
--- a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -12,7 +12,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether or not to enable the Jenkins Job Builder (JJB) service. It
           allows defining jobs for Jenkins in a declarative manner.
 
@@ -24,15 +24,15 @@ in {
           deleted.
 
           Please see the Jenkins Job Builder documentation for more info:
-          <link xlink:href="http://docs.openstack.org/infra/jenkins-job-builder/">
-          http://docs.openstack.org/infra/jenkins-job-builder/</link>
+          [
+          http://docs.openstack.org/infra/jenkins-job-builder/](http://docs.openstack.org/infra/jenkins-job-builder/)
         '';
       };
 
       accessUser = mkOption {
-        default = "";
+        default = "admin";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User id in Jenkins used to reload config.
         '';
       };
@@ -40,19 +40,20 @@ in {
       accessToken = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User token in Jenkins used to reload config.
           WARNING: This token will be world readable in the Nix store. To keep
-          it secret, use the <option>accessTokenFile</option> option instead.
+          it secret, use the {option}`accessTokenFile` option instead.
         '';
       };
 
       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 = ''
-          File containing the API token for the <option>accessUser</option>
+        description = lib.mdDoc ''
+          File containing the API token for the {option}`accessUser`
           user.
         '';
       };
@@ -66,7 +67,7 @@ in {
               builders:
                 - shell: echo 'Hello world!'
         '';
-        description = ''
+        description = lib.mdDoc ''
           Job descriptions for Jenkins Job Builder in YAML format.
         '';
       };
@@ -86,7 +87,7 @@ in {
             '''
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Job descriptions for Jenkins Job Builder in JSON format.
         '';
       };
@@ -104,7 +105,7 @@ in {
             }
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Job descriptions for Jenkins Job Builder in Nix format.
 
           This is a trivial wrapper around jsonJobs, using builtins.toJSON
@@ -156,12 +157,22 @@ in {
           reloadScript = ''
             echo "Asking Jenkins to reload config"
             curl_opts="--silent --fail --show-error"
-            access_token=${if cfg.accessTokenFile != ""
-                           then "$(cat '${cfg.accessTokenFile}')"
-                           else cfg.accessToken}
-            jenkins_url="http://${cfg.accessUser}:$access_token@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}"
-            crumb=$(curl $curl_opts "$jenkins_url"'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
-            curl $curl_opts -X POST -H "$crumb" "$jenkins_url"/reload
+            access_token_file=${if cfg.accessTokenFile != ""
+                           then cfg.accessTokenFile
+                           else "$RUNTIME_DIRECTORY/jenkins_access_token.txt"}
+            if [ "${cfg.accessToken}" != "" ]; then
+               (umask 0077; printf "${cfg.accessToken}" >"$access_token_file")
+            fi
+            jenkins_url="http://${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}"
+            auth_file="$RUNTIME_DIRECTORY/jenkins_auth_file.txt"
+            trap 'rm -f "$auth_file"' EXIT
+            (umask 0077; printf "${cfg.accessUser}:@password_placeholder@" >"$auth_file")
+            "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "$access_token_file" "$auth_file"
+
+            if ! "${pkgs.jenkins}/bin/jenkins-cli" -s "$jenkins_url" -auth "@$auth_file" reload-configuration; then
+                echo "error: failed to reload configuration"
+                exit 1
+            fi
           '';
         in
           ''
@@ -233,6 +244,7 @@ in {
             done
           '' + (if cfg.accessUser != "" then reloadScript else "");
       serviceConfig = {
+        Type = "oneshot";
         User = jenkinsCfg.user;
         RuntimeDirectory = "jenkins-job-builder";
       };
diff --git a/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixos/modules/services/continuous-integration/jenkins/slave.nix
index 871b9914fb27..9b86917ab380 100644
--- a/nixos/modules/services/continuous-integration/jenkins/slave.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/slave.nix
@@ -14,7 +14,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If true the system will be configured to work as a jenkins slave.
           If the system is also configured to work as a jenkins master then this has no effect.
           In progress: Currently only assures the jenkins user is configured.
@@ -24,7 +24,7 @@ in {
       user = mkOption {
         default = "jenkins";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User the jenkins slave agent should execute under.
         '';
       };
@@ -32,7 +32,7 @@ in {
       group = mkOption {
         default = "jenkins";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           If the default slave agent user "jenkins" is configured then this is
           the primary group of that user.
         '';
@@ -41,7 +41,7 @@ in {
       home = mkOption {
         default = "/var/lib/jenkins";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path to use as JENKINS_HOME. If the default user "jenkins" is configured then
           this is the home of the "jenkins" user.
         '';
@@ -50,7 +50,7 @@ in {
       javaPackage = mkOption {
         default = pkgs.jdk;
         defaultText = literalExpression "pkgs.jdk";
-        description = ''
+        description = lib.mdDoc ''
           Java package to install.
         '';
         type = types.package;
diff --git a/nixos/modules/services/databases/aerospike.nix b/nixos/modules/services/databases/aerospike.nix
index 8109762aea78..21df4cd0577b 100644
--- a/nixos/modules/services/databases/aerospike.nix
+++ b/nixos/modules/services/databases/aerospike.nix
@@ -39,19 +39,19 @@ in
   options = {
 
     services.aerospike = {
-      enable = mkEnableOption "Aerospike server";
+      enable = mkEnableOption (lib.mdDoc "Aerospike server");
 
       package = mkOption {
         default = pkgs.aerospike;
         defaultText = literalExpression "pkgs.aerospike";
         type = types.package;
-        description = "Which Aerospike derivation to use";
+        description = lib.mdDoc "Which Aerospike derivation to use";
       };
 
       workDir = mkOption {
         type = types.str;
         default = "/var/lib/aerospike";
-        description = "Location where Aerospike stores its files";
+        description = lib.mdDoc "Location where Aerospike stores its files";
       };
 
       networkConfig = mkOption {
@@ -80,7 +80,7 @@ in
             port 3003
           }
         '';
-        description = "network section of configuration file";
+        description = lib.mdDoc "network section of configuration file";
       };
 
       extraConfig = mkOption {
@@ -94,7 +94,7 @@ in
             storage-engine memory
           }
         '';
-        description = "Extra configuration";
+        description = lib.mdDoc "Extra configuration";
       };
     };
 
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index b36cac35e7c2..e26acb88d8c8 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -4,11 +4,12 @@ let
   inherit (lib)
     concatStringsSep
     flip
-    literalDocBook
+    literalMD
     literalExpression
     optionalAttrs
     optionals
     recursiveUpdate
+    mdDoc
     mkEnableOption
     mkIf
     mkOption
@@ -18,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 (
@@ -38,7 +43,7 @@ let
           parameters = [{ seeds = concatStringsSep "," cfg.seedAddresses; }];
         }
       ];
-    } // optionalAttrs (versionAtLeast cfg.package.version "3") {
+    } // optionalAttrs atLeast3 {
       hints_directory = "${cfg.homeDir}/hints";
     }
   );
@@ -61,7 +66,7 @@ let
     cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
 
     passAsFile = [ "extraEnvSh" ];
-    inherit (cfg) extraEnvSh;
+    inherit (cfg) extraEnvSh package;
 
     buildCommand = ''
       mkdir -p "$out"
@@ -79,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/
+      ''}
     '';
   };
 
@@ -94,20 +103,32 @@ 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;
       default = "Test Cluster";
-      description = ''
+      description = mdDoc ''
         The name of the cluster.
         This setting prevents nodes in one logical cluster from joining
         another. All nodes in a cluster must have the same value.
@@ -117,19 +138,19 @@ in
     user = mkOption {
       type = types.str;
       default = defaultUser;
-      description = "Run Apache Cassandra under this user.";
+      description = mdDoc "Run Apache Cassandra under this user.";
     };
 
     group = mkOption {
       type = types.str;
       default = defaultUser;
-      description = "Run Apache Cassandra under this group.";
+      description = mdDoc "Run Apache Cassandra under this group.";
     };
 
     homeDir = mkOption {
       type = types.path;
       default = "/var/lib/cassandra";
-      description = ''
+      description = mdDoc ''
         Home directory for Apache Cassandra.
       '';
     };
@@ -139,7 +160,7 @@ in
       default = pkgs.cassandra;
       defaultText = literalExpression "pkgs.cassandra";
       example = literalExpression "pkgs.cassandra_3_11";
-      description = ''
+      description = mdDoc ''
         The Apache Cassandra package to use.
       '';
     };
@@ -147,8 +168,8 @@ in
     jvmOpts = mkOption {
       type = types.listOf types.str;
       default = [ ];
-      description = ''
-        Populate the JVM_OPT environment variable.
+      description = mdDoc ''
+        Populate the `JVM_OPT` environment variable.
       '';
     };
 
@@ -156,20 +177,20 @@ in
       type = types.nullOr types.str;
       default = "127.0.0.1";
       example = null;
-      description = ''
+      description = mdDoc ''
         Address or interface to bind to and tell other Cassandra nodes
         to connect to. You _must_ change this if you want multiple
         nodes to be able to communicate!
 
-        Set listenAddress OR listenInterface, not both.
+        Set {option}`listenAddress` OR {option}`listenInterface`, not both.
 
         Leaving it blank leaves it up to
-        InetAddress.getLocalHost(). This will always do the Right
-        Thing _if_ the node is properly configured (hostname, name
+        `InetAddress.getLocalHost()`. This will always do the "Right
+        Thing" _if_ the node is properly configured (hostname, name
         resolution, etc), and the Right Thing is to use the address
         associated with the hostname (it might not be).
 
-        Setting listen_address to 0.0.0.0 is always wrong.
+        Setting {option}`listenAddress` to `0.0.0.0` is always wrong.
       '';
     };
 
@@ -177,8 +198,8 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "eth1";
-      description = ''
-        Set listenAddress OR listenInterface, not both. Interfaces
+      description = mdDoc ''
+        Set `listenAddress` OR `listenInterface`, not both. Interfaces
         must correspond to a single address, IP aliasing is not
         supported.
       '';
@@ -188,18 +209,18 @@ in
       type = types.nullOr types.str;
       default = "127.0.0.1";
       example = null;
-      description = ''
+      description = mdDoc ''
         The address or interface to bind the native transport server to.
 
-        Set rpcAddress OR rpcInterface, not both.
+        Set {option}`rpcAddress` OR {option}`rpcInterface`, not both.
 
-        Leaving rpcAddress blank has the same effect as on
-        listenAddress (i.e. it will be based on the configured hostname
+        Leaving {option}`rpcAddress` blank has the same effect as on
+        {option}`listenAddress` (i.e. it will be based on the configured hostname
         of the node).
 
-        Note that unlike listenAddress, you can specify 0.0.0.0, but you
-        must also set extraConfig.broadcast_rpc_address to a value other
-        than 0.0.0.0.
+        Note that unlike {option}`listenAddress`, you can specify `"0.0.0.0"`, but you
+        must also set `extraConfig.broadcast_rpc_address` to a value other
+        than `"0.0.0.0"`.
 
         For security reasons, you should not expose this port to the
         internet. Firewall it if needed.
@@ -210,8 +231,8 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "eth1";
-      description = ''
-        Set rpcAddress OR rpcInterface, not both. Interfaces must
+      description = mdDoc ''
+        Set {option}`rpcAddress` OR {option}`rpcInterface`, not both. Interfaces must
         correspond to a single address, IP aliasing is not supported.
       '';
     };
@@ -233,7 +254,7 @@ in
           <logger name="com.thinkaurelius.thrift" level="ERROR"/>
         </configuration>
       '';
-      description = ''
+      description = mdDoc ''
         XML logback configuration for cassandra
       '';
     };
@@ -241,24 +262,24 @@ in
     seedAddresses = mkOption {
       type = types.listOf types.str;
       default = [ "127.0.0.1" ];
-      description = ''
+      description = mdDoc ''
         The addresses of hosts designated as contact points in the cluster. A
         joining node contacts one of the nodes in the seeds list to learn the
         topology of the ring.
-        Set to 127.0.0.1 for a single node cluster.
+        Set to `[ "127.0.0.1" ]` for a single node cluster.
       '';
     };
 
     allowClients = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = mdDoc ''
         Enables or disables the native transport server (CQL binary protocol).
-        This server uses the same address as the <literal>rpcAddress</literal>,
-        but the port it uses is not <literal>rpc_port</literal> but
-        <literal>native_transport_port</literal>. See the official Cassandra
+        This server uses the same address as the {option}`rpcAddress`,
+        but the port it uses is not `rpc_port` but
+        `native_transport_port`. See the official Cassandra
         docs for more information on these variables and set them using
-        <literal>extraConfig</literal>.
+        {option}`extraConfig`.
       '';
     };
 
@@ -269,8 +290,8 @@ in
         {
           commitlog_sync_batch_window_in_ms = 3;
         };
-      description = ''
-        Extra options to be merged into cassandra.yaml as nix attribute set.
+      description = mdDoc ''
+        Extra options to be merged into {file}`cassandra.yaml` as nix attribute set.
       '';
     };
 
@@ -278,8 +299,8 @@ in
       type = types.lines;
       default = "";
       example = literalExpression ''"CLASSPATH=$CLASSPATH:''${extraJar}"'';
-      description = ''
-        Extra shell lines to be appended onto cassandra-env.sh.
+      description = mdDoc ''
+        Extra shell lines to be appended onto {file}`cassandra-env.sh`.
       '';
     };
 
@@ -287,13 +308,13 @@ in
       type = types.nullOr types.str;
       default = "3w";
       example = null;
-      description = ''
+      description = mdDoc ''
         Set the interval how often full repairs are run, i.e.
-        <literal>nodetool repair --full</literal> is executed. See
-        https://cassandra.apache.org/doc/latest/operating/repair.html
+        {command}`nodetool repair --full` is executed. See
+        <https://cassandra.apache.org/doc/latest/operating/repair.html>
         for more information.
 
-        Set to <literal>null</literal> to disable full repairs.
+        Set to `null` to disable full repairs.
       '';
     };
 
@@ -301,7 +322,7 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "--partitioner-range" ];
-      description = ''
+      description = mdDoc ''
         Options passed through to the full repair command.
       '';
     };
@@ -310,13 +331,13 @@ in
       type = types.nullOr types.str;
       default = "3d";
       example = null;
-      description = ''
+      description = mdDoc ''
         Set the interval how often incremental repairs are run, i.e.
-        <literal>nodetool repair</literal> is executed. See
-        https://cassandra.apache.org/doc/latest/operating/repair.html
+        {command}`nodetool repair` is executed. See
+        <https://cassandra.apache.org/doc/latest/operating/repair.html>
         for more information.
 
-        Set to <literal>null</literal> to disable incremental repairs.
+        Set to `null` to disable incremental repairs.
       '';
     };
 
@@ -324,7 +345,7 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "--partitioner-range" ];
-      description = ''
+      description = mdDoc ''
         Options passed through to the incremental repair command.
       '';
     };
@@ -333,15 +354,15 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "4G";
-      description = ''
-        Must be left blank or set together with heapNewSize.
+      description = mdDoc ''
+        Must be left blank or set together with {option}`heapNewSize`.
         If left blank a sensible value for the available amount of RAM and CPU
         cores is calculated.
 
         Override to set the amount of memory to allocate to the JVM at
         start-up. For production use you may wish to adjust this for your
-        environment. MAX_HEAP_SIZE is the total amount of memory dedicated
-        to the Java heap. HEAP_NEWSIZE refers to the size of the young
+        environment. `MAX_HEAP_SIZE` is the total amount of memory dedicated
+        to the Java heap. `HEAP_NEWSIZE` refers to the size of the young
         generation.
 
         The main trade-off for the young generation is that the larger it
@@ -354,21 +375,21 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "800M";
-      description = ''
-        Must be left blank or set together with heapNewSize.
+      description = mdDoc ''
+        Must be left blank or set together with {option}`heapNewSize`.
         If left blank a sensible value for the available amount of RAM and CPU
         cores is calculated.
 
         Override to set the amount of memory to allocate to the JVM at
         start-up. For production use you may wish to adjust this for your
-        environment. HEAP_NEWSIZE refers to the size of the young
+        environment. `HEAP_NEWSIZE` refers to the size of the young
         generation.
 
         The main trade-off for the young generation is that the larger it
         is, the longer GC pause times will be. The shorter it is, the more
         expensive GC will be (usually).
 
-        The example HEAP_NEWSIZE assumes a modern 8-core+ machine for decent pause
+        The example `HEAP_NEWSIZE` assumes a modern 8-core+ machine for decent pause
         times. If in doubt, and if you do not particularly want to tweak, go with
         100 MB per physical CPU core.
       '';
@@ -378,7 +399,7 @@ in
       type = types.nullOr types.int;
       default = null;
       example = 4;
-      description = ''
+      description = mdDoc ''
         Set this to control the amount of arenas per-thread in glibc.
       '';
     };
@@ -386,19 +407,19 @@ in
     remoteJmx = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = mdDoc ''
         Cassandra ships with JMX accessible *only* from localhost.
         To enable remote JMX connections set to true.
 
         Be sure to also enable authentication and/or TLS.
-        See: https://wiki.apache.org/cassandra/JmxSecurity
+        See: <https://wiki.apache.org/cassandra/JmxSecurity>
       '';
     };
 
     jmxPort = mkOption {
       type = types.int;
       default = 7199;
-      description = ''
+      description = mdDoc ''
         Specifies the default port over which Cassandra will be available for
         JMX connections.
         For security reasons, you should not expose this port to the internet.
@@ -408,11 +429,11 @@ in
 
     jmxRoles = mkOption {
       default = [ ];
-      description = ''
-        Roles that are allowed to access the JMX (e.g. nodetool)
-        BEWARE: The passwords will be stored world readable in the nix-store.
+      description = mdDoc ''
+        Roles that are allowed to access the JMX (e.g. {command}`nodetool`)
+        BEWARE: The passwords will be stored world readable in the nix store.
                 It's recommended to use your own protected file using
-                <literal>jmxRolesFile</literal>
+                {option}`jmxRolesFile`
 
         Doesn't work in versions older than 3.11 because they don't like that
         it's world readable.
@@ -421,11 +442,11 @@ in
         options = {
           username = mkOption {
             type = types.str;
-            description = "Username for JMX";
+            description = lib.mdDoc "Username for JMX";
           };
           password = mkOption {
             type = types.str;
-            description = "Password for JMX";
+            description = lib.mdDoc "Password for JMX";
           };
         };
       });
@@ -434,12 +455,12 @@ 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 = literalDocBook ''generated configuration file if version is at least 3.11, otherwise <literal>null</literal>'';
+      defaultText = literalMD ''generated configuration file if version is at least 3.11, otherwise `null`'';
       example = "/var/lib/cassandra/jmx.password";
-      description = ''
+      description = lib.mdDoc ''
         Specify your own jmx roles file.
 
         Make sure the permissions forbid "others" from reading the file if
@@ -485,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;
@@ -507,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;
@@ -535,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/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix
index 3a161d56107e..04dd20b5f14d 100644
--- a/nixos/modules/services/databases/clickhouse.nix
+++ b/nixos/modules/services/databases/clickhouse.nix
@@ -11,13 +11,13 @@ 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";
-        description = ''
+        defaultText = lib.literalExpression "pkgs.clickhouse";
+        description = lib.mdDoc ''
           ClickHouse package to use.
         '';
       };
diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix
index 9a7aebe4f6ae..26ccb030b3df 100644
--- a/nixos/modules/services/databases/cockroachdb.nix
+++ b/nixos/modules/services/databases/cockroachdb.nix
@@ -35,13 +35,13 @@ let
     address = mkOption {
       type = types.str;
       default = "localhost";
-      description = "Address to bind to for ${descr}";
+      description = lib.mdDoc "Address to bind to for ${descr}";
     };
 
     port = mkOption {
       type = types.port;
       default = defaultPort;
-      description = "Port to bind to for ${descr}";
+      description = lib.mdDoc "Port to bind to for ${descr}";
     };
   };
 in
@@ -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,62 +68,62 @@ 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>
+          ```
         '';
       };
 
       join = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "The addresses for connecting the node to a cluster.";
+        description = lib.mdDoc "The addresses for connecting the node to a cluster.";
       };
 
       insecure = mkOption {
         type = types.bool;
         default = false;
-        description = "Run in insecure mode.";
+        description = lib.mdDoc "Run in insecure mode.";
       };
 
       certsDir = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "The path to the certificate directory.";
+        description = lib.mdDoc "The path to the certificate directory.";
       };
 
       user = mkOption {
         type = types.str;
         default = "cockroachdb";
-        description = "User account under which CockroachDB runs";
+        description = lib.mdDoc "User account under which CockroachDB runs";
       };
 
       group = mkOption {
         type = types.str;
         default = "cockroachdb";
-        description = "User account under which CockroachDB runs";
+        description = lib.mdDoc "User account under which CockroachDB runs";
       };
 
       openPorts = mkOption {
         type = types.bool;
         default = false;
-        description = "Open firewall ports for cluster communication by default";
+        description = lib.mdDoc "Open firewall ports for cluster communication by default";
       };
 
       cache = mkOption {
         type = types.str;
         default = "25%";
-        description = ''
+        description = lib.mdDoc ''
           The total size for caches.
 
           This can be a percentage, expressed with a fraction sign or as a
           decimal-point number, or any bytes-based unit. For example,
-          <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+          `"25%"`, `"0.25"` both represent
           25% of the available system memory. The values
-          <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+          `"1000000000"` and `"1GB"` both
           represent 1 gigabyte of memory.
 
         '';
@@ -132,15 +132,15 @@ in
       maxSqlMemory = mkOption {
         type = types.str;
         default = "25%";
-        description = ''
+        description = lib.mdDoc ''
           The maximum in-memory storage capacity available to store temporary
           data for SQL queries.
 
           This can be a percentage, expressed with a fraction sign or as a
           decimal-point number, or any bytes-based unit. For example,
-          <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+          `"25%"`, `"0.25"` both represent
           25% of the available system memory. The values
-          <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+          `"1000000000"` and `"1GB"` both
           represent 1 gigabyte of memory.
         '';
       };
@@ -149,7 +149,7 @@ in
         type = types.package;
         default = pkgs.cockroachdb;
         defaultText = literalExpression "pkgs.cockroachdb";
-        description = ''
+        description = lib.mdDoc ''
           The CockroachDB derivation to use for running the service.
 
           This would primarily be useful to enable Enterprise Edition features
@@ -162,9 +162,9 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "--advertise-addr" "[fe80::f6f2:::]" ];
-        description = ''
-          Extra CLI arguments passed to <command>cockroach start</command>.
-          For the full list of supported argumemnts, check <link xlink:href="https://www.cockroachlabs.com/docs/stable/cockroach-start.html#flags"/>
+        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>
         '';
       };
     };
diff --git a/nixos/modules/services/databases/couchdb.nix b/nixos/modules/services/databases/couchdb.nix
index 742e605d224d..cdf32654e663 100644
--- a/nixos/modules/services/databases/couchdb.nix
+++ b/nixos/modules/services/databases/couchdb.nix
@@ -34,19 +34,13 @@ in {
 
     services.couchdb = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to run CouchDB Server.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "CouchDB Server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.couchdb3;
         defaultText = literalExpression "pkgs.couchdb3";
-        description = ''
+        description = lib.mdDoc ''
           CouchDB package to use.
         '';
       };
@@ -54,7 +48,7 @@ in {
       adminUser = mkOption {
         type = types.str;
         default = "admin";
-        description = ''
+        description = lib.mdDoc ''
           Couchdb (i.e. fauxton) account with permission for all dbs and
           tasks.
         '';
@@ -63,7 +57,7 @@ in {
       adminPass = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Couchdb (i.e. fauxton) account with permission for all dbs and
           tasks.
         '';
@@ -72,7 +66,7 @@ in {
       user = mkOption {
         type = types.str;
         default = "couchdb";
-        description = ''
+        description = lib.mdDoc ''
           User account under which couchdb runs.
         '';
       };
@@ -80,7 +74,7 @@ in {
       group = mkOption {
         type = types.str;
         default = "couchdb";
-        description = ''
+        description = lib.mdDoc ''
           Group account under which couchdb runs.
         '';
       };
@@ -90,7 +84,7 @@ in {
       databaseDir = mkOption {
         type = types.path;
         default = "/var/lib/couchdb";
-        description = ''
+        description = lib.mdDoc ''
           Specifies location of CouchDB database files (*.couch named). This
           location should be writable and readable for the user the CouchDB
           service runs as (couchdb by default).
@@ -100,7 +94,7 @@ in {
       uriFile = mkOption {
         type = types.path;
         default = "/run/couchdb/couchdb.uri";
-        description = ''
+        description = lib.mdDoc ''
           This file contains the full URI that can be used to access this
           instance of CouchDB. It is used to help discover the port CouchDB is
           running on (if it was set to 0 (e.g. automatically assigned any free
@@ -112,7 +106,7 @@ in {
       viewIndexDir = mkOption {
         type = types.path;
         default = "/var/lib/couchdb";
-        description = ''
+        description = lib.mdDoc ''
           Specifies location of CouchDB view index files. This location should
           be writable and readable for the user that runs the CouchDB service
           (couchdb by default).
@@ -122,15 +116,15 @@ in {
       bindAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           Defines the IP address by which CouchDB will be accessible.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 5984;
-        description = ''
+        description = lib.mdDoc ''
           Defined the port number to listen.
         '';
       };
@@ -138,7 +132,7 @@ in {
       logFile = mkOption {
         type = types.path;
         default = "/var/log/couchdb.log";
-        description = ''
+        description = lib.mdDoc ''
           Specifies the location of file for logging output.
         '';
       };
@@ -146,7 +140,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration. Overrides any other cofiguration.
         '';
       };
@@ -155,14 +149,14 @@ in {
         type = types.path;
         default = "${cfg.package}/etc/vm.args";
         defaultText = literalExpression ''"config.${opt.package}/etc/vm.args"'';
-        description = ''
+        description = lib.mdDoc ''
           vm.args configuration. Overrides Couchdb's Erlang VM parameters file.
         '';
       };
 
       configFile = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Configuration file for persisting runtime changes. File
           needs to be readable and writable from couchdb user/group.
         '';
@@ -193,6 +187,11 @@ in {
 
       preStart = ''
         touch ${cfg.configFile}
+        if ! test -e ${cfg.databaseDir}/.erlang.cookie; then
+          touch ${cfg.databaseDir}/.erlang.cookie
+          chmod 600 ${cfg.databaseDir}/.erlang.cookie
+          dd if=/dev/random bs=16 count=1 | base64 > ${cfg.databaseDir}/.erlang.cookie
+        fi
       '';
 
       environment = {
@@ -204,6 +203,7 @@ in {
         ERL_FLAGS= ''-couch_ini ${cfg.package}/etc/default.ini ${configFile} ${pkgs.writeText "couchdb-extra.ini" cfg.extraConfig} ${cfg.configFile}'';
         # 5. the vm.args file
         COUCHDB_ARGS_FILE=''${cfg.argsFile}'';
+        HOME =''${cfg.databaseDir}'';
       };
 
       serviceConfig = {
diff --git a/nixos/modules/services/databases/dgraph.nix b/nixos/modules/services/databases/dgraph.nix
new file mode 100644
index 000000000000..5726851a43f9
--- /dev/null
+++ b/nixos/modules/services/databases/dgraph.nix
@@ -0,0 +1,148 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dgraph;
+  settingsFormat = pkgs.formats.json {};
+  configFile = settingsFormat.generate "config.json" cfg.settings;
+  dgraphWithNode = pkgs.runCommand "dgraph" {
+    nativeBuildInputs = [ pkgs.makeWrapper ];
+  }
+  ''
+    mkdir -p $out/bin
+    makeWrapper ${cfg.package}/bin/dgraph $out/bin/dgraph \
+      --set PATH '${lib.makeBinPath [ pkgs.nodejs ]}:$PATH' \
+  '';
+  securityOptions = {
+      NoNewPrivileges = true;
+
+      AmbientCapabilities = "";
+      CapabilityBoundingSet = "";
+
+      DeviceAllow = "";
+
+      LockPersonality = true;
+
+      PrivateTmp = true;
+      PrivateDevices = true;
+      PrivateUsers = true;
+
+      ProtectClock = true;
+      ProtectControlGroups = true;
+      ProtectHostname = true;
+      ProtectKernelLogs = true;
+      ProtectKernelModules = true;
+      ProtectKernelTunables = true;
+
+      RemoveIPC = true;
+
+      RestrictNamespaces = true;
+      RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+      RestrictRealtime = true;
+      RestrictSUIDSGID = true;
+
+      SystemCallArchitectures = "native";
+      SystemCallErrorNumber = "EPERM";
+      SystemCallFilter = [
+        "@system-service"
+        "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
+      ];
+  };
+in
+{
+  options = {
+    services.dgraph = {
+      enable = mkEnableOption (lib.mdDoc "Dgraph native GraphQL database with a graph backend");
+
+      package = lib.mkPackageOption pkgs "dgraph" { };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = {};
+        description = lib.mdDoc ''
+          Contents of the dgraph config. For more details see https://dgraph.io/docs/deploy/config
+        '';
+      };
+
+      alpha = {
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = lib.mdDoc ''
+            The host which dgraph alpha will be run on.
+          '';
+        };
+        port = mkOption {
+          type = types.port;
+          default = 7080;
+          description = lib.mdDoc ''
+            The port which to run dgraph alpha on.
+          '';
+        };
+
+      };
+
+      zero = {
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = lib.mdDoc ''
+            The host which dgraph zero will be run on.
+          '';
+        };
+        port = mkOption {
+          type = types.port;
+          default = 5080;
+          description = lib.mdDoc ''
+            The port which to run dgraph zero on.
+          '';
+        };
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.dgraph.settings = {
+      badger.compression = mkDefault "zstd:3";
+    };
+
+    systemd.services.dgraph-zero = {
+      description = "Dgraph native GraphQL database with a graph backend. Zero controls node clustering";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        StateDirectory = "dgraph-zero";
+        WorkingDirectory = "/var/lib/dgraph-zero";
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/dgraph zero --my ${cfg.zero.host}:${toString cfg.zero.port}";
+        Restart = "on-failure";
+      } // securityOptions;
+    };
+
+    systemd.services.dgraph-alpha = {
+      description = "Dgraph native GraphQL database with a graph backend. Alpha serves data";
+      after = [ "network.target" "dgraph-zero.service" ];
+      requires = [ "dgraph-zero.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        StateDirectory = "dgraph-alpha";
+        WorkingDirectory = "/var/lib/dgraph-alpha";
+        DynamicUser = true;
+        ExecStart = "${dgraphWithNode}/bin/dgraph alpha --config ${configFile} --my ${cfg.alpha.host}:${toString cfg.alpha.port} --zero ${cfg.zero.host}:${toString cfg.zero.port}";
+        ExecStop = ''
+          ${pkgs.curl}/bin/curl --data "mutation { shutdown { response { message code } } }" \
+              --header 'Content-Type: application/graphql' \
+              -X POST \
+              http://localhost:8080/admin
+        '';
+        Restart = "on-failure";
+      } // securityOptions;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ happysalada ];
+}
diff --git a/nixos/modules/services/databases/dragonflydb.nix b/nixos/modules/services/databases/dragonflydb.nix
new file mode 100644
index 000000000000..46a0c188c3ae
--- /dev/null
+++ b/nixos/modules/services/databases/dragonflydb.nix
@@ -0,0 +1,152 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dragonflydb;
+  dragonflydb = pkgs.dragonflydb;
+
+  settings =
+    {
+      port = cfg.port;
+      dir = "/var/lib/dragonflydb";
+      keys_output_limit = cfg.keysOutputLimit;
+    } //
+    (lib.optionalAttrs (cfg.bind != null) { bind = cfg.bind; }) //
+    (lib.optionalAttrs (cfg.requirePass != null) { requirepass = cfg.requirePass; }) //
+    (lib.optionalAttrs (cfg.maxMemory != null) { maxmemory = cfg.maxMemory; }) //
+    (lib.optionalAttrs (cfg.memcachePort != null) { memcache_port = cfg.memcachePort; }) //
+    (lib.optionalAttrs (cfg.dbNum != null) { dbnum = cfg.dbNum; }) //
+    (lib.optionalAttrs (cfg.cacheMode != null) { cache_mode = cfg.cacheMode; });
+in
+{
+
+  ###### interface
+
+  options = {
+    services.dragonflydb = {
+      enable = mkEnableOption (lib.mdDoc "DragonflyDB");
+
+      user = mkOption {
+        type = types.str;
+        default = "dragonfly";
+        description = lib.mdDoc "The user to run DragonflyDB as";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 6379;
+        description = lib.mdDoc "The TCP port to accept connections.";
+      };
+
+      bind = mkOption {
+        type = with types; nullOr str;
+        default = "127.0.0.1";
+        description = lib.mdDoc ''
+          The IP interface to bind to.
+          `null` means "all interfaces".
+        '';
+      };
+
+      requirePass = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = lib.mdDoc "Password for database";
+        example = "letmein!";
+      };
+
+      maxMemory = mkOption {
+        type = with types; nullOr ints.unsigned;
+        default = null;
+        description = lib.mdDoc ''
+          The maximum amount of memory to use for storage (in bytes).
+          `null` means this will be automatically set.
+        '';
+      };
+
+      memcachePort = mkOption {
+        type = with types; nullOr port;
+        default = null;
+        description = lib.mdDoc ''
+          To enable memcached compatible API on this port.
+          `null` means disabled.
+        '';
+      };
+
+      keysOutputLimit = mkOption {
+        type = types.ints.unsigned;
+        default = 8192;
+        description = lib.mdDoc ''
+          Maximum number of returned keys in keys command.
+          `keys` is a dangerous command.
+          We truncate its result to avoid blowup in memory when fetching too many keys.
+        '';
+      };
+
+      dbNum = mkOption {
+        type = with types; nullOr ints.unsigned;
+        default = null;
+        description = lib.mdDoc "Maximum number of supported databases for `select`";
+      };
+
+      cacheMode = mkOption {
+        type = with types; nullOr bool;
+        default = null;
+        description = lib.mdDoc ''
+          Once this mode is on, Dragonfly will evict items least likely to be stumbled
+          upon in the future but only when it is near maxmemory limit.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.dragonflydb.enable {
+
+    users.users = optionalAttrs (cfg.user == "dragonfly") {
+      dragonfly.description = "DragonflyDB server user";
+      dragonfly.isSystemUser = true;
+      dragonfly.group = "dragonfly";
+    };
+    users.groups = optionalAttrs (cfg.user == "dragonfly") { dragonfly = { }; };
+
+    environment.systemPackages = [ dragonflydb ];
+
+    systemd.services.dragonflydb = {
+      description = "DragonflyDB server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart = "${dragonflydb}/bin/dragonfly --alsologtostderr ${builtins.concatStringsSep " " (attrsets.mapAttrsToList (n: v: "--${n} ${strings.escapeShellArg v}") settings)}";
+
+        User = cfg.user;
+
+        # Filesystem access
+        ReadWritePaths = [ settings.dir ];
+        StateDirectory = "dragonflydb";
+        StateDirectoryMode = "0700";
+        # Process Properties
+        LimitMEMLOCK = "infinity";
+        # Caps
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        LockPersonality = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        MemoryDenyWriteExecute = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix
index 4e3130bea22f..4c2855345368 100644
--- a/nixos/modules/services/databases/firebird.nix
+++ b/nixos/modules/services/databases/firebird.nix
@@ -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,23 +40,23 @@ in
 
     services.firebird = {
 
-      enable = mkEnableOption "the Firebird super server";
+      enable = mkEnableOption (lib.mdDoc "the Firebird super server");
 
       package = mkOption {
         default = pkgs.firebird;
         defaultText = literalExpression "pkgs.firebird";
         type = types.package;
         example = literalExpression "pkgs.firebird_3";
-        description = ''
-          Which Firebird package to be installed: <code>pkgs.firebird_3</code>
-          For SuperServer use override: <code>pkgs.firebird_3.override { superServer = true; };</code>
+        description = lib.mdDoc ''
+          Which Firebird package to be installed: `pkgs.firebird_3`
+          For SuperServer use override: `pkgs.firebird_3.override { superServer = true; };`
         '';
       };
 
       port = mkOption {
         default = 3050;
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           Port Firebird uses.
         '';
       };
@@ -64,7 +64,7 @@ in
       user = mkOption {
         default = "firebird";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User account under which firebird runs.
         '';
       };
@@ -72,7 +72,7 @@ in
       baseDir = mkOption {
         default = "/var/lib/firebird";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Location containing data/ and system/ directories.
           data/ stores the databases, system/ stores the password database security2.fdb.
         '';
diff --git a/nixos/modules/services/databases/foundationdb.nix b/nixos/modules/services/databases/foundationdb.nix
index e22127403e91..16d539b661eb 100644
--- a/nixos/modules/services/databases/foundationdb.nix
+++ b/nixos/modules/services/databases/foundationdb.nix
@@ -62,11 +62,11 @@ in
 {
   options.services.foundationdb = {
 
-    enable = mkEnableOption "FoundationDB Server";
+    enable = mkEnableOption (lib.mdDoc "FoundationDB Server");
 
     package = mkOption {
       type        = types.package;
-      description = ''
+      description = lib.mdDoc ''
         The FoundationDB package to use for this server. This must be specified by the user
         in order to ensure migrations and upgrades are controlled appropriately.
       '';
@@ -75,19 +75,19 @@ in
     publicAddress = mkOption {
       type        = types.str;
       default     = "auto";
-      description = "Publicly visible IP address of the process. Port is determined by process ID";
+      description = lib.mdDoc "Publicly visible IP address of the process. Port is determined by process ID";
     };
 
     listenAddress = mkOption {
       type        = types.str;
       default     = "public";
-      description = "Publicly visible IP address of the process. Port is determined by process ID";
+      description = lib.mdDoc "Publicly visible IP address of the process. Port is determined by process ID";
     };
 
     listenPortStart = mkOption {
       type          = types.int;
       default       = 4500;
-      description   = ''
+      description   = lib.mdDoc ''
         Starting port number for database listening sockets. Every FDB process binds to a
         subsequent port, to this number reflects the start of the overall range. e.g. having
         8 server processes will use all ports between 4500 and 4507.
@@ -97,52 +97,52 @@ 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.*`.
       '';
     };
 
     dataDir = mkOption {
       type        = types.path;
       default     = "/var/lib/foundationdb";
-      description = "Data directory. All cluster data will be put under here.";
+      description = lib.mdDoc "Data directory. All cluster data will be put under here.";
     };
 
     logDir = mkOption {
       type        = types.path;
       default     = "/var/log/foundationdb";
-      description = "Log directory.";
+      description = lib.mdDoc "Log directory.";
     };
 
     user = mkOption {
       type        = types.str;
       default     = "foundationdb";
-      description = "User account under which FoundationDB runs.";
+      description = lib.mdDoc "User account under which FoundationDB runs.";
     };
 
     group = mkOption {
       type        = types.str;
       default     = "foundationdb";
-      description = "Group account under which FoundationDB runs.";
+      description = lib.mdDoc "Group account under which FoundationDB runs.";
     };
 
     class = mkOption {
       type        = types.nullOr (types.enum [ "storage" "transaction" "stateless" ]);
       default     = null;
-      description = "Process class";
+      description = lib.mdDoc "Process class";
     };
 
     restartDelay = mkOption {
       type = types.int;
       default = 10;
-      description = "Number of seconds to wait before restarting servers.";
+      description = lib.mdDoc "Number of seconds to wait before restarting servers.";
     };
 
     logSize = mkOption {
       type        = types.str;
       default     = "10MiB";
-      description = ''
+      description = lib.mdDoc ''
         Roll over to a new log file after the current log file
         reaches the specified size.
       '';
@@ -151,7 +151,7 @@ in
     maxLogSize = mkOption {
       type        = types.str;
       default     = "100MiB";
-      description = ''
+      description = lib.mdDoc ''
         Delete the oldest log file when the total size of all log
         files exceeds the specified size. If set to 0, old log files
         will not be deleted.
@@ -161,33 +161,33 @@ in
     serverProcesses = mkOption {
       type = types.int;
       default = 1;
-      description = "Number of fdbserver processes to run.";
+      description = lib.mdDoc "Number of fdbserver processes to run.";
     };
 
     backupProcesses = mkOption {
       type = types.int;
       default = 1;
-      description = "Number of backup_agent processes to run for snapshots.";
+      description = lib.mdDoc "Number of backup_agent processes to run for snapshots.";
     };
 
     memory = mkOption {
       type        = types.str;
       default     = "8GiB";
-      description = ''
+      description = lib.mdDoc ''
         Maximum memory used by the process. The default value is
-        <literal>8GiB</literal>. When specified without a unit,
-        <literal>MiB</literal> is assumed. This parameter does not
+        `8GiB`. When specified without a unit,
+        `MiB` is assumed. This parameter does not
         change the memory allocation of the program. Rather, it sets
         a hard limit beyond which the process will kill itself and
-        be restarted. The default value of <literal>8GiB</literal>
+        be restarted. The default value of `8GiB`
         is double the intended memory usage in the default
         configuration (providing an emergency buffer to deal with
         memory leaks or similar problems). It is not recommended to
         decrease the value of this parameter below its default
         value. It may be increased if you wish to allocate a very
         large amount of storage engine memory or cache. In
-        particular, when the <literal>storageMemory</literal>
-        parameter is increased, the <literal>memory</literal>
+        particular, when the `storageMemory`
+        parameter is increased, the `memory`
         parameter should be increased by an equal amount.
       '';
     };
@@ -195,22 +195,22 @@ in
     storageMemory = mkOption {
       type        = types.str;
       default     = "1GiB";
-      description = ''
+      description = lib.mdDoc ''
         Maximum memory used for data storage. The default value is
-        <literal>1GiB</literal>. When specified without a unit,
-        <literal>MB</literal> is assumed. Clusters using the memory
+        `1GiB`. When specified without a unit,
+        `MB` is assumed. Clusters using the memory
         storage engine will be restricted to using this amount of
         memory per process for purposes of data storage. Memory
         overhead associated with storing the data is counted against
         this total. If you increase the
-        <literal>storageMemory</literal>, you should also increase
-        the <literal>memory</literal> parameter by the same amount.
+        `storageMemory`, you should also increase
+        the `memory` parameter by the same amount.
       '';
     };
 
     tls = mkOption {
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         FoundationDB Transport Security Layer (TLS) settings.
       '';
 
@@ -218,7 +218,7 @@ in
         options = {
           certificate = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               Path to the TLS certificate file. This certificate will
               be offered to, and may be verified by, clients.
             '';
@@ -226,13 +226,13 @@ in
 
           key = mkOption {
             type = types.str;
-            description = "Private key file for the certificate.";
+            description = lib.mdDoc "Private key file for the certificate.";
           };
 
           allowedPeers = mkOption {
             type = types.str;
             default = "Check.Valid=1,Check.Unexpired=1";
-            description = ''
+            description = lib.mdDoc ''
               "Peer verification string". This may be used to adjust which TLS
               client certificates a server will accept, as a form of user
               authorization; for example, it may only accept TLS clients who
@@ -253,7 +253,7 @@ in
         dataHall     = null;
       };
 
-      description = ''
+      description = lib.mdDoc ''
         FoundationDB locality settings.
       '';
 
@@ -262,7 +262,7 @@ in
           machineId = mkOption {
             default = null;
             type = types.nullOr types.str;
-            description = ''
+            description = lib.mdDoc ''
               Machine identifier key. All processes on a machine should share a
               unique id. By default, processes on a machine determine a unique id to share.
               This does not generally need to be set.
@@ -272,7 +272,7 @@ in
           zoneId = mkOption {
             default = null;
             type = types.nullOr types.str;
-            description = ''
+            description = lib.mdDoc ''
               Zone identifier key. Processes that share a zone id are
               considered non-unique for the purposes of data replication.
               If unset, defaults to machine id.
@@ -282,7 +282,7 @@ in
           datacenterId = mkOption {
             default = null;
             type = types.nullOr types.str;
-            description = ''
+            description = lib.mdDoc ''
               Data center identifier key. All processes physically located in a
               data center should share the id. If you are depending on data
               center based replication this must be set on all processes.
@@ -292,7 +292,7 @@ in
           dataHall = mkOption {
             default = null;
             type = types.nullOr types.str;
-            description = ''
+            description = lib.mdDoc ''
               Data hall identifier key. All processes physically located in a
               data hall should share the id. If you are depending on data
               hall based replication this must be set on all processes.
@@ -305,7 +305,7 @@ in
     extraReadWritePaths = mkOption {
       default = [ ];
       type = types.listOf types.path;
-      description = ''
+      description = lib.mdDoc ''
         An extra set of filesystem paths that FoundationDB can read to
         and write from. By default, FoundationDB runs under a heavily
         namespaced systemd environment without write access to most of
@@ -319,13 +319,13 @@ in
     pidfile = mkOption {
       type        = types.path;
       default     = "/run/foundationdb.pid";
-      description = "Path to pidfile for fdbmonitor.";
+      description = lib.mdDoc "Path to pidfile for fdbmonitor.";
     };
 
     traceFormat = mkOption {
       type = types.enum [ "xml" "json" ];
       default = "xml";
-      description = "Trace logging format.";
+      description = lib.mdDoc "Trace logging format.";
     };
   };
 
diff --git a/nixos/modules/services/databases/hbase.nix b/nixos/modules/services/databases/hbase-standalone.nix
index fe4f05eec643..1ee73ec8d1ff 100644
--- a/nixos/modules/services/databases/hbase.nix
+++ b/nixos/modules/services/databases/hbase-standalone.nix
@@ -3,8 +3,8 @@
 with lib;
 
 let
-  cfg = config.services.hbase;
-  opt = options.services.hbase;
+  cfg = config.services.hbase-standalone;
+  opt = options.services.hbase-standalone;
 
   buildProperty = configAttr:
     (builtins.concatStringsSep "\n"
@@ -32,25 +32,25 @@ let
 
 in {
 
+  imports = [
+    (mkRenamedOptionModule [ "services" "hbase" ] [ "services" "hbase-standalone" ])
+  ];
+
   ###### interface
 
   options = {
+    services.hbase-standalone = {
 
-    services.hbase = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to run HBase.
-        '';
-      };
+      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;
         default = pkgs.hbase;
         defaultText = literalExpression "pkgs.hbase";
-        description = ''
+        description = lib.mdDoc ''
           HBase package to use.
         '';
       };
@@ -59,7 +59,7 @@ in {
       user = mkOption {
         type = types.str;
         default = "hbase";
-        description = ''
+        description = lib.mdDoc ''
           User account under which HBase runs.
         '';
       };
@@ -67,7 +67,7 @@ in {
       group = mkOption {
         type = types.str;
         default = "hbase";
-        description = ''
+        description = lib.mdDoc ''
           Group account under which HBase runs.
         '';
       };
@@ -75,7 +75,7 @@ in {
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/hbase";
-        description = ''
+        description = lib.mdDoc ''
           Specifies location of HBase database files. This location should be
           writable and readable for the user the HBase service runs as
           (hbase by default).
@@ -85,7 +85,7 @@ in {
       logDir = mkOption {
         type = types.path;
         default = "/var/log/hbase";
-        description = ''
+        description = lib.mdDoc ''
           Specifies the location of HBase log files.
         '';
       };
@@ -102,18 +102,17 @@ in {
             "hbase.zookeeper.property.dataDir" = "''${config.${opt.dataDir}}/zookeeper";
           }
         '';
-        description = ''
-          configurations in hbase-site.xml, see <link xlink:href="https://github.com/apache/hbase/blob/master/hbase-server/src/test/resources/hbase-site.xml"/> for details.
+        description = lib.mdDoc ''
+          configurations in hbase-site.xml, see <https://github.com/apache/hbase/blob/master/hbase-server/src/test/resources/hbase-site.xml> for details.
         '';
       };
 
     };
-
   };
 
   ###### implementation
 
-  config = mkIf config.services.hbase.enable {
+  config = mkIf cfg.enable {
 
     systemd.tmpfiles.rules = [
       "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix
index f7383b2023a4..b3361d2014ca 100644
--- a/nixos/modules/services/databases/influxdb.nix
+++ b/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
   '';
@@ -114,38 +112,38 @@ in
 
       enable = mkOption {
         default = false;
-        description = "Whether to enable the influxdb server";
+        description = lib.mdDoc "Whether to enable the influxdb server";
         type = types.bool;
       };
 
       package = mkOption {
         default = pkgs.influxdb;
         defaultText = literalExpression "pkgs.influxdb";
-        description = "Which influxdb derivation to use";
+        description = lib.mdDoc "Which influxdb derivation to use";
         type = types.package;
       };
 
       user = mkOption {
         default = "influxdb";
-        description = "User account under which influxdb runs";
+        description = lib.mdDoc "User account under which influxdb runs";
         type = types.str;
       };
 
       group = mkOption {
         default = "influxdb";
-        description = "Group under which influxdb runs";
+        description = lib.mdDoc "Group under which influxdb runs";
         type = types.str;
       };
 
       dataDir = mkOption {
         default = "/var/db/influxdb";
-        description = "Data directory for influxd data files.";
+        description = lib.mdDoc "Data directory for influxd data files.";
         type = types.path;
       };
 
       extraConfig = mkOption {
         default = {};
-        description = "Extra configuration options for influxdb";
+        description = lib.mdDoc "Extra configuration options for influxdb";
         type = types.attrs;
       };
     };
diff --git a/nixos/modules/services/databases/influxdb2.nix b/nixos/modules/services/databases/influxdb2.nix
index 340c515bbb43..e74de66ddc2f 100644
--- a/nixos/modules/services/databases/influxdb2.nix
+++ b/nixos/modules/services/databases/influxdb2.nix
@@ -10,18 +10,18 @@ in
 {
   options = {
     services.influxdb2 = {
-      enable = mkEnableOption "the influxdb2 server";
+      enable = mkEnableOption (lib.mdDoc "the influxdb2 server");
 
       package = mkOption {
         default = pkgs.influxdb2-server;
         defaultText = literalExpression "pkgs.influxdb2";
-        description = "influxdb2 derivation to use.";
+        description = lib.mdDoc "influxdb2 derivation to use.";
         type = types.package;
       };
 
       settings = mkOption {
         default = { };
-        description = ''configuration options for influxdb2, see <link xlink:href="https://docs.influxdata.com/influxdb/v2.0/reference/config-options"/> for details.'';
+        description = lib.mdDoc ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
         type = format.type;
       };
     };
@@ -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/nixos/modules/services/databases/memcached.nix b/nixos/modules/services/databases/memcached.nix
index 1c06937e2f30..542c80ab2e67 100644
--- a/nixos/modules/services/databases/memcached.nix
+++ b/nixos/modules/services/databases/memcached.nix
@@ -17,44 +17,44 @@ in
   options = {
 
     services.memcached = {
-      enable = mkEnableOption "Memcached";
+      enable = mkEnableOption (lib.mdDoc "Memcached");
 
       user = mkOption {
         type = types.str;
         default = "memcached";
-        description = "The user to run Memcached as";
+        description = lib.mdDoc "The user to run Memcached as";
       };
 
       listen = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "The IP address to bind to.";
+        description = lib.mdDoc "The IP address to bind to.";
       };
 
       port = mkOption {
         type = types.port;
         default = 11211;
-        description = "The port to bind to.";
+        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;
         default = 64;
-        description = "The maximum amount of memory to use for storage, in megabytes.";
+        description = lib.mdDoc "The maximum amount of memory to use for storage, in megabytes.";
       };
 
       maxConnections = mkOption {
         type = types.ints.unsigned;
         default = 1024;
-        description = "The maximum number of simultaneous connections.";
+        description = lib.mdDoc "The maximum number of simultaneous connections.";
       };
 
       extraOptions = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "A list of extra options that will be added as a suffix when running memcached.";
+        description = lib.mdDoc "A list of extra options that will be added as a suffix when running memcached.";
       };
     };
 
diff --git a/nixos/modules/services/databases/monetdb.nix b/nixos/modules/services/databases/monetdb.nix
index 52a2ef041f8b..5573b530a913 100644
--- a/nixos/modules/services/databases/monetdb.nix
+++ b/nixos/modules/services/databases/monetdb.nix
@@ -12,44 +12,44 @@ in {
   options = {
     services.monetdb = {
 
-      enable = mkEnableOption "the MonetDB database server";
+      enable = mkEnableOption (lib.mdDoc "the MonetDB database server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.monetdb;
         defaultText = literalExpression "pkgs.monetdb";
-        description = "MonetDB package to use.";
+        description = lib.mdDoc "MonetDB package to use.";
       };
 
       user = mkOption {
         type = types.str;
         default = "monetdb";
-        description = "User account under which MonetDB runs.";
+        description = lib.mdDoc "User account under which MonetDB runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "monetdb";
-        description = "Group under which MonetDB runs.";
+        description = lib.mdDoc "Group under which MonetDB runs.";
       };
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/monetdb";
-        description = "Data directory for the dbfarm.";
+        description = lib.mdDoc "Data directory for the dbfarm.";
       };
 
       port = mkOption {
         type = types.ints.u16;
         default = 50000;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
         example = "0.0.0.0";
-        description = "Address to listen on.";
+        description = lib.mdDoc "Address to listen on.";
       };
     };
   };
diff --git a/nixos/modules/services/databases/mongodb.nix b/nixos/modules/services/databases/mongodb.nix
index fccf85d482e0..211133de63fe 100644
--- a/nixos/modules/services/databases/mongodb.nix
+++ b/nixos/modules/services/databases/mongodb.nix
@@ -29,63 +29,63 @@ 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 {
         type = types.str;
         default = "mongodb";
-        description = "User account under which MongoDB runs";
+        description = lib.mdDoc "User account under which MongoDB runs";
       };
 
       bind_ip = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "IP to bind to";
+        description = lib.mdDoc "IP to bind to";
       };
 
       quiet = mkOption {
         type = types.bool;
         default = false;
-        description = "quieter output";
+        description = lib.mdDoc "quieter output";
       };
 
       enableAuth = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable client authentication. Creates a default superuser with username root!";
+        description = lib.mdDoc "Enable client authentication. Creates a default superuser with username root!";
       };
 
       initialRootPassword = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Password for the root user if auth is enabled.";
+        description = lib.mdDoc "Password for the root user if auth is enabled.";
       };
 
       dbpath = mkOption {
         type = types.str;
         default = "/var/db/mongodb";
-        description = "Location where MongoDB stores its files";
+        description = lib.mdDoc "Location where MongoDB stores its files";
       };
 
       pidFile = mkOption {
         type = types.str;
         default = "/run/mongodb.pid";
-        description = "Location of MongoDB pid file";
+        description = lib.mdDoc "Location of MongoDB pid file";
       };
 
       replSetName = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           If this instance is part of a replica set, set its name here.
           Otherwise, leave empty to run as single node.
         '';
@@ -97,13 +97,13 @@ in
         example = ''
           storage.journal.enabled: false
         '';
-        description = "MongoDB extra configuration in YAML format";
+        description = lib.mdDoc "MongoDB extra configuration in YAML format";
       };
 
       initialScript = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A file containing MongoDB statements to execute on first startup.
         '';
       };
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 625b31d081c9..128bb0862175 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/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>
+          :::
         '';
       };
 
@@ -88,9 +88,9 @@ in
         defaultText = ''
           A configuration file automatically generated by NixOS.
         '';
-        description = ''
+        description = lib.mdDoc ''
           Override the configuration file used by MySQL. By default,
-          NixOS generates one automatically from <option>services.mysql.settings</option>.
+          NixOS generates one automatically from {option}`services.mysql.settings`.
         '';
         example = literalExpression ''
           pkgs.writeText "my.cnf" '''
@@ -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 ''
           {
@@ -143,14 +141,14 @@ in
           options = {
             name = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The name of the database to create.
               '';
             };
             schema = mkOption {
               type = types.nullOr types.path;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The initial schema of the database; if null (the default),
                 an empty database is created.
               '';
@@ -158,26 +156,28 @@ in
           };
         });
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           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 {
         type = types.nullOr types.path;
         default = null;
-        description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database.";
+        description = lib.mdDoc "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database.";
       };
 
       ensureDatabases = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Ensures that the specified databases exist.
           This option will never delete existing databases, especially not when the value of this
           option is changed. This means that databases created once through this option or
@@ -194,14 +194,14 @@ in
           options = {
             name = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Name of the user to ensure.
               '';
             };
             ensurePermissions = mkOption {
               type = types.attrsOf types.str;
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 Permissions to ensure for the user, specified as attribute set.
                 The attribute names specify the database and tables to grant the permissions for,
                 separated by a dot. You may use wildcards here.
@@ -210,8 +210,8 @@ in
 
                 For more information on how to specify the target
                 and on which privileges exist, see the
-                <link xlink:href="https://mariadb.com/kb/en/library/grant/">GRANT syntax</link>.
-                The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>.
+                [GRANT syntax](https://mariadb.com/kb/en/library/grant/).
+                The attributes are used as `GRANT ''${attrName} ON ''${attrValue}`.
               '';
               example = literalExpression ''
                 {
@@ -223,7 +223,7 @@ in
           };
         });
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Ensures that the specified users exist and have at least the ensured permissions.
           The MySQL users will be identified using Unix socket authentication. This authenticates the Unix user with the
           same name only, and that without the need for a password.
@@ -253,39 +253,39 @@ in
         role = mkOption {
           type = types.enum [ "master" "slave" "none" ];
           default = "none";
-          description = "Role of the MySQL server instance.";
+          description = lib.mdDoc "Role of the MySQL server instance.";
         };
 
         serverId = mkOption {
           type = types.int;
           default = 1;
-          description = "Id of the MySQL server instance. This number must be unique for each instance.";
+          description = lib.mdDoc "Id of the MySQL server instance. This number must be unique for each instance.";
         };
 
         masterHost = mkOption {
           type = types.str;
-          description = "Hostname of the MySQL master server.";
+          description = lib.mdDoc "Hostname of the MySQL master server.";
         };
 
         slaveHost = mkOption {
           type = types.str;
-          description = "Hostname of the MySQL slave server.";
+          description = lib.mdDoc "Hostname of the MySQL slave server.";
         };
 
         masterUser = mkOption {
           type = types.str;
-          description = "Username of the MySQL replication user.";
+          description = lib.mdDoc "Username of the MySQL replication user.";
         };
 
         masterPassword = mkOption {
           type = types.str;
-          description = "Password of the MySQL replication user.";
+          description = lib.mdDoc "Password of the MySQL replication user.";
         };
 
         masterPort = mkOption {
           type = types.port;
           default = 3306;
-          description = "Port number on which the MySQL master server runs.";
+          description = lib.mdDoc "Port number on which the MySQL master server runs.";
         };
       };
     };
diff --git a/nixos/modules/services/databases/neo4j.nix b/nixos/modules/services/databases/neo4j.nix
index 8816f3b2e4b6..d78ff8390e4d 100644
--- a/nixos/modules/services/databases/neo4j.nix
+++ b/nixos/modules/services/databases/neo4j.nix
@@ -36,48 +36,43 @@ let
   serverConfig = pkgs.writeText "neo4j.conf" ''
     # General
     dbms.allow_upgrade=${boolToString cfg.allowUpgrade}
-    dbms.connectors.default_listen_address=${cfg.defaultListenAddress}
-    dbms.read_only=${boolToString cfg.readOnly}
+    dbms.default_listen_address=${cfg.defaultListenAddress}
+    dbms.databases.default_to_read_only=${boolToString cfg.readOnly}
     ${optionalString (cfg.workerCount > 0) ''
       dbms.threads.worker_count=${toString cfg.workerCount}
     ''}
 
-    # Directories
+    # Directories (readonly)
     dbms.directories.certificates=${cfg.directories.certificates}
-    dbms.directories.data=${cfg.directories.data}
-    dbms.directories.logs=${cfg.directories.home}/logs
     dbms.directories.plugins=${cfg.directories.plugins}
+    dbms.directories.lib=${cfg.package}/share/neo4j/lib
     ${optionalString (cfg.constrainLoadCsv) ''
       dbms.directories.import=${cfg.directories.imports}
-    ''}
+   ''}
+
+    # Directories (read and write)
+    dbms.directories.data=${cfg.directories.data}
+    dbms.directories.logs=${cfg.directories.home}/logs
+    dbms.directories.run=${cfg.directories.home}/run
 
     # HTTP Connector
     ${optionalString (cfg.http.enable) ''
       dbms.connector.http.enabled=${boolToString cfg.http.enable}
       dbms.connector.http.listen_address=${cfg.http.listenAddress}
-    ''}
-    ${optionalString (!cfg.http.enable) ''
-      # It is not possible to disable the HTTP connector. To fully prevent
-      # clients from connecting to HTTP, block the HTTP port (7474 by default)
-      # via firewall. listen_address is set to the loopback interface to
-      # prevent remote clients from connecting.
-      dbms.connector.http.listen_address=127.0.0.1
+      dbms.connector.http.advertised_address=${cfg.http.listenAddress}
     ''}
 
     # HTTPS Connector
     dbms.connector.https.enabled=${boolToString cfg.https.enable}
     dbms.connector.https.listen_address=${cfg.https.listenAddress}
-    https.ssl_policy=${cfg.https.sslPolicy}
+    dbms.connector.https.advertised_address=${cfg.https.listenAddress}
 
     # BOLT Connector
     dbms.connector.bolt.enabled=${boolToString cfg.bolt.enable}
     dbms.connector.bolt.listen_address=${cfg.bolt.listenAddress}
-    bolt.ssl_policy=${cfg.bolt.sslPolicy}
+    dbms.connector.bolt.advertised_address=${cfg.bolt.listenAddress}
     dbms.connector.bolt.tls_level=${cfg.bolt.tlsLevel}
 
-    # neo4j-shell
-    dbms.shell.enabled=${boolToString cfg.shell.enable}
-
     # SSL Policies
     ${concatStringsSep "\n" sslPolicies}
 
@@ -95,8 +90,10 @@ let
     dbms.jvm.additional=-Djdk.tls.rejectClientInitiatedRenegotiation=true
     dbms.jvm.additional=-Dunsupported.dbms.udc.source=tarball
 
-    # Usage Data Collector
-    dbms.udc.enabled=${boolToString cfg.udc.enable}
+    #dbms.memory.heap.initial_size=12000m
+    #dbms.memory.heap.max_size=12000m
+    #dbms.memory.pagecache.size=4g
+    #dbms.tx_state.max_off_heap_memory=8000m
 
     # Extra Configuration
     ${cfg.extraServerConfig}
@@ -114,6 +111,8 @@ in {
     (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.")
     (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.")
     (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.")
+    (mkRemovedOptionModule [ "services" "neo4j" "shell" "enabled" ] "shell.enabled was removed upstream")
+    (mkRemovedOptionModule [ "services" "neo4j" "udc" "enabled" ] "udc.enabled was removed upstream")
   ];
 
   ###### interface
@@ -123,7 +122,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable Neo4j Community Edition.
       '';
     };
@@ -131,7 +130,7 @@ in {
     allowUpgrade = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Allow upgrade of Neo4j database files from an older version.
       '';
     };
@@ -139,15 +138,14 @@ in {
     constrainLoadCsv = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Sets the root directory for file URLs used with the Cypher
-        <literal>LOAD CSV</literal> clause to be that defined by
-        <option>directories.imports</option>. It restricts
+        `LOAD CSV` clause to be that defined by
+        {option}`directories.imports`. It restricts
         access to only those files within that directory and its
         subdirectories.
-        </para>
-        <para>
-        Setting this option to <literal>false</literal> introduces
+
+        Setting this option to `false` introduces
         possible security problems.
       '';
     };
@@ -155,24 +153,23 @@ in {
     defaultListenAddress = mkOption {
       type = types.str;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         Default network interface to listen for incoming connections. To
         listen for connections on all interfaces, use "0.0.0.0".
-        </para>
-        <para>
+
         Specifies the default IP address and address part of connector
-        specific <option>listenAddress</option> options. To bind specific
+        specific {option}`listenAddress` options. To bind specific
         connectors to a specific network interfaces, specify the entire
-        <option>listenAddress</option> option for that connector.
+        {option}`listenAddress` option for that connector.
       '';
     };
 
     extraServerConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra configuration for Neo4j Community server. Refer to the
-        <link xlink:href="https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/">complete reference</link>
+        [complete reference](https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/)
         of Neo4j configuration settings.
       '';
     };
@@ -181,7 +178,7 @@ in {
       type = types.package;
       default = pkgs.neo4j;
       defaultText = literalExpression "pkgs.neo4j";
-      description = ''
+      description = lib.mdDoc ''
         Neo4j package to use.
       '';
     };
@@ -189,7 +186,7 @@ in {
     readOnly = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Only allow read operations from this Neo4j instance.
       '';
     };
@@ -197,9 +194,9 @@ in {
     workerCount = mkOption {
       type = types.ints.between 0 44738;
       default = 0;
-      description = ''
+      description = lib.mdDoc ''
         Number of Neo4j worker threads, where the default of
-        <literal>0</literal> indicates a worker count equal to the number of
+        `0` indicates a worker count equal to the number of
         available processors.
       '';
     };
@@ -208,9 +205,9 @@ in {
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable the BOLT 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 BOLT port (7687 by default).
         '';
       };
@@ -218,36 +215,34 @@ in {
       listenAddress = mkOption {
         type = types.str;
         default = ":7687";
-        description = ''
+        description = lib.mdDoc ''
           Neo4j listen address for BOLT traffic. The listen address is
-          expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+          expressed in the format `<ip-address>:<port-number>`.
         '';
       };
 
       sslPolicy = mkOption {
         type = types.str;
         default = "legacy";
-        description = ''
+        description = lib.mdDoc ''
           Neo4j SSL policy for BOLT traffic.
-          </para>
-          <para>
+
           The legacy policy is a special policy which is not defined in
           the policy configuration section, but rather derives from
-          <option>directories.certificates</option> and
-          associated files (by default: <filename>neo4j.key</filename> and
-          <filename>neo4j.cert</filename>). Its use will be deprecated.
-          </para>
-          <para>
+          {option}`directories.certificates` and
+          associated files (by default: {file}`neo4j.key` and
+          {file}`neo4j.cert`). Its use will be deprecated.
+
           Note: This connector must be configured to support/require
           SSL/TLS for the legacy policy to actually be utilized. See
-          <option>bolt.tlsLevel</option>.
+          {option}`bolt.tlsLevel`.
         '';
       };
 
       tlsLevel = mkOption {
         type = types.enum [ "REQUIRED" "OPTIONAL" "DISABLED" ];
         default = "OPTIONAL";
-        description = ''
+        description = lib.mdDoc ''
           SSL/TSL requirement level for BOLT traffic.
         '';
       };
@@ -258,21 +253,19 @@ in {
         type = types.path;
         default = "${cfg.directories.home}/certificates";
         defaultText = literalExpression ''"''${config.${opt.directories.home}}/certificates"'';
-        description = ''
+        description = lib.mdDoc ''
           Directory for storing certificates to be used by Neo4j for
           TLS connections.
-          </para>
-          <para>
+
           When setting this directory to something other than its default,
           ensure the directory's existence, and that read/write permissions are
-          given to the Neo4j daemon user <literal>neo4j</literal>.
-          </para>
-          <para>
+          given to the Neo4j daemon user `neo4j`.
+
           Note that changing this directory from its default will prevent
           the directory structure required for each SSL policy from being
           automatically generated. A policy's directory structure as defined by
-          its <option>baseDirectory</option>,<option>revokedDir</option> and
-          <option>trustedDir</option> must then be setup manually. The
+          its {option}`baseDirectory`,{option}`revokedDir` and
+          {option}`trustedDir` must then be setup manually. The
           existence of these directories is mandatory, as well as the presence
           of the certificate file and the private key. Ensure the correct
           permissions are set on these directories and files.
@@ -283,25 +276,24 @@ in {
         type = types.path;
         default = "${cfg.directories.home}/data";
         defaultText = literalExpression ''"''${config.${opt.directories.home}}/data"'';
-        description = ''
+        description = lib.mdDoc ''
           Path of the data directory. You must not configure more than one
           Neo4j installation to use the same data directory.
-          </para>
-          <para>
+
           When setting this directory to something other than its default,
           ensure the directory's existence, and that read/write permissions are
-          given to the Neo4j daemon user <literal>neo4j</literal>.
+          given to the Neo4j daemon user `neo4j`.
         '';
       };
 
       home = mkOption {
         type = types.path;
         default = "/var/lib/neo4j";
-        description = ''
+        description = lib.mdDoc ''
           Path of the Neo4j home directory. Other default directories are
           subdirectories of this path. This directory will be created if
-          non-existent, and its ownership will be <command>chown</command> to
-          the Neo4j daemon user <literal>neo4j</literal>.
+          non-existent, and its ownership will be {command}`chown` to
+          the Neo4j daemon user `neo4j`.
         '';
       };
 
@@ -309,16 +301,15 @@ in {
         type = types.path;
         default = "${cfg.directories.home}/import";
         defaultText = literalExpression ''"''${config.${opt.directories.home}}/import"'';
-        description = ''
+        description = lib.mdDoc ''
           The root directory for file URLs used with the Cypher
-          <literal>LOAD CSV</literal> clause. Only meaningful when
-          <option>constrainLoadCvs</option> is set to
-          <literal>true</literal>.
-          </para>
-          <para>
+          `LOAD CSV` clause. Only meaningful when
+          {option}`constrainLoadCvs` is set to
+          `true`.
+
           When setting this directory to something other than its default,
           ensure the directory's existence, and that read permission is
-          given to the Neo4j daemon user <literal>neo4j</literal>.
+          given to the Neo4j daemon user `neo4j`.
         '';
       };
 
@@ -326,15 +317,14 @@ in {
         type = types.path;
         default = "${cfg.directories.home}/plugins";
         defaultText = literalExpression ''"''${config.${opt.directories.home}}/plugins"'';
-        description = ''
+        description = lib.mdDoc ''
           Path of the database plugin directory. Compiled Java JAR files that
           contain database procedures will be loaded if they are placed in
           this directory.
-          </para>
-          <para>
+
           When setting this directory to something other than its default,
           ensure the directory's existence, and that read permission is
-          given to the Neo4j daemon user <literal>neo4j</literal>.
+          given to the Neo4j daemon user `neo4j`.
         '';
       };
     };
@@ -343,22 +333,19 @@ in {
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          The HTTP connector is required for Neo4j, and cannot be disabled.
-          Setting this option to <literal>false</literal> will force the HTTP
-          connector's <option>listenAddress</option> to the loopback
-          interface to prevent connection of remote clients. To prevent all
-          clients from connecting, block the HTTP port (7474 by default) by
-          firewall.
+        description = lib.mdDoc ''
+          Enable the HTTP connector for Neo4j. Setting this option to
+          `false` will stop Neo4j from listening for incoming
+          connections on the HTTPS port (7474 by default).
         '';
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = ":7474";
-        description = ''
+        description = lib.mdDoc ''
           Neo4j listen address for HTTP traffic. The listen address is
-          expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+          expressed in the format `<ip-address>:<port-number>`.
         '';
       };
     };
@@ -367,9 +354,9 @@ in {
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable the HTTPS 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 (7473 by default).
         '';
       };
@@ -377,24 +364,23 @@ in {
       listenAddress = mkOption {
         type = types.str;
         default = ":7473";
-        description = ''
+        description = lib.mdDoc ''
           Neo4j listen address for HTTPS traffic. The listen address is
-          expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+          expressed in the format `<ip-address>:<port-number>`.
         '';
       };
 
       sslPolicy = mkOption {
         type = types.str;
         default = "legacy";
-        description = ''
+        description = lib.mdDoc ''
           Neo4j SSL policy for HTTPS traffic.
-          </para>
-          <para>
+
           The legacy policy is a special policy which is not defined in the
           policy configuration section, but rather derives from
-          <option>directories.certificates</option> and
-          associated files (by default: <filename>neo4j.key</filename> and
-          <filename>neo4j.cert</filename>). Its use will be deprecated.
+          {option}`directories.certificates` and
+          associated files (by default: {file}`neo4j.key` and
+          {file}`neo4j.cert`). Its use will be deprecated.
         '';
       };
     };
@@ -403,9 +389,9 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable a remote shell server which Neo4j Shell clients can log in to.
-          Only applicable to <command>neo4j-shell</command>.
+          Only applicable to {command}`neo4j-shell`.
         '';
       };
     };
@@ -417,18 +403,16 @@ in {
           allowKeyGeneration = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Allows the generation of a private key and associated self-signed
               certificate. Only performed when both objects cannot be found for
               this policy. It is recommended to turn this off again after keys
               have been generated.
-              </para>
-              <para>
+
               The public certificate is required to be duplicated to the
               directory holding trusted certificates as defined by the
-              <option>trustedDir</option> option.
-              </para>
-              <para>
+              {option}`trustedDir` option.
+
               Keys should in general be generated and distributed offline by a
               trusted certificate authority and not by utilizing this mode.
             '';
@@ -438,17 +422,16 @@ in {
             type = types.path;
             default = "${cfg.directories.certificates}/${name}";
             defaultText = literalExpression ''"''${config.${opt.directories.certificates}}/''${name}"'';
-            description = ''
+            description = lib.mdDoc ''
               The mandatory base directory for cryptographic objects of this
               policy. This path is only automatically generated when this
-              option as well as <option>directories.certificates</option> are
+              option as well as {option}`directories.certificates` are
               left at their default. Ensure read/write permissions are given
-              to the Neo4j daemon user <literal>neo4j</literal>.
-              </para>
-              <para>
+              to the Neo4j daemon user `neo4j`.
+
               It is also possible to override each individual
               configuration with absolute paths. See the
-              <option>privateKey</option> and <option>publicCertificate</option>
+              {option}`privateKey` and {option}`publicCertificate`
               policy options.
             '';
           };
@@ -456,7 +439,7 @@ in {
           ciphers = mkOption {
             type = types.nullOr (types.listOf types.str);
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Restrict the allowed ciphers of this policy to those defined
               here. The default ciphers are those of the JVM platform.
             '';
@@ -465,7 +448,7 @@ in {
           clientAuth = mkOption {
             type = types.enum [ "NONE" "OPTIONAL" "REQUIRE" ];
             default = "REQUIRE";
-            description = ''
+            description = lib.mdDoc ''
               The client authentication stance for this policy.
             '';
           };
@@ -473,9 +456,9 @@ in {
           privateKey = mkOption {
             type = types.str;
             default = "private.key";
-            description = ''
+            description = lib.mdDoc ''
               The name of private PKCS #8 key file for this policy to be found
-              in the <option>baseDirectory</option>, or the absolute path to
+              in the {option}`baseDirectory`, or the absolute path to
               the key file. It is mandatory that a key can be found or generated.
             '';
           };
@@ -483,16 +466,15 @@ in {
           publicCertificate = mkOption {
             type = types.str;
             default = "public.crt";
-            description = ''
+            description = lib.mdDoc ''
               The name of public X.509 certificate (chain) file in PEM format
-              for this policy to be found in the <option>baseDirectory</option>,
+              for this policy to be found in the {option}`baseDirectory`,
               or the absolute path to the certificate file. It is mandatory
               that a certificate can be found or generated.
-              </para>
-              <para>
+
               The public certificate is required to be duplicated to the
               directory holding trusted certificates as defined by the
-              <option>trustedDir</option> option.
+              {option}`trustedDir` option.
             '';
           };
 
@@ -500,22 +482,22 @@ in {
             type = types.path;
             default = "${config.baseDirectory}/revoked";
             defaultText = literalExpression ''"''${config.${options.baseDirectory}}/revoked"'';
-            description = ''
+            description = lib.mdDoc ''
               Path to directory of CRLs (Certificate Revocation Lists) in
               PEM format. Must be an absolute path. The existence of this
               directory is mandatory and will need to be created manually when:
               setting this option to something other than its default; setting
-              either this policy's <option>baseDirectory</option> or
-              <option>directories.certificates</option> to something other than
+              either this policy's {option}`baseDirectory` or
+              {option}`directories.certificates` to something other than
               their default. Ensure read/write permissions are given to the
-              Neo4j daemon user <literal>neo4j</literal>.
+              Neo4j daemon user `neo4j`.
             '';
           };
 
           tlsVersions = mkOption {
             type = types.listOf types.str;
             default = [ "TLSv1.2" ];
-            description = ''
+            description = lib.mdDoc ''
               Restrict the TLS protocol versions of this policy to those
               defined here.
             '';
@@ -524,7 +506,7 @@ in {
           trustAll = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Makes this policy trust all remote parties. Enabling this is not
               recommended and the policy's trusted directory will be ignored.
               Use of this mode is discouraged. It would offer encryption but
@@ -536,19 +518,18 @@ in {
             type = types.path;
             default = "${config.baseDirectory}/trusted";
             defaultText = literalExpression ''"''${config.${options.baseDirectory}}/trusted"'';
-            description = ''
+            description = lib.mdDoc ''
               Path to directory of X.509 certificates in PEM format for
               trusted parties. Must be an absolute path. The existence of this
               directory is mandatory and will need to be created manually when:
               setting this option to something other than its default; setting
-              either this policy's <option>baseDirectory</option> or
-              <option>directories.certificates</option> to something other than
+              either this policy's {option}`baseDirectory` or
+              {option}`directories.certificates` to something other than
               their default. Ensure read/write permissions are given to the
-              Neo4j daemon user <literal>neo4j</literal>.
-              </para>
-              <para>
+              Neo4j daemon user `neo4j`.
+
               The public certificate as defined by
-              <option>publicCertificate</option> is required to be duplicated
+              {option}`publicCertificate` is required to be duplicated
               to this directory.
             '';
           };
@@ -557,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
@@ -573,29 +554,16 @@ in {
 
       }));
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Defines the SSL policies for use with Neo4j connectors. Each attribute
         of this set defines a policy, with the attribute name defining the name
         of the policy and its namespace. Refer to the operations manual section
         on Neo4j's
-        <link xlink:href="https://neo4j.com/docs/operations-manual/current/security/ssl-framework/">SSL Framework</link>
+        [SSL Framework](https://neo4j.com/docs/operations-manual/current/security/ssl-framework/)
         for further details.
       '';
     };
 
-    udc = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable the Usage Data Collector which Neo4j uses to collect usage
-          data. Refer to the operations manual section on the
-          <link xlink:href="https://neo4j.com/docs/operations-manual/current/configuration/usage-data-collector/">Usage Data Collector</link>
-          for more information.
-        '';
-      };
-    };
-
   };
 
   ###### implementation
@@ -627,7 +595,7 @@ in {
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
         environment = {
-          NEO4J_HOME = "${cfg.package}/share/neo4j";
+          NEO4J_HOME = "${cfg.directories.home}";
           NEO4J_CONF = "${cfg.directories.home}/conf";
         };
         serviceConfig = {
@@ -668,6 +636,6 @@ in {
     };
 
   meta = {
-    maintainers = with lib.maintainers; [ patternspandemic ];
+    maintainers = with lib.maintainers; [ patternspandemic jonringer erictapen ];
   };
 }
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index 2c1e25d43084..cba3442023cb 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -3,7 +3,6 @@
 with lib;
 let
   cfg = config.services.openldap;
-  legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ];
   openldap = cfg.package;
   configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d";
 
@@ -11,7 +10,15 @@ let
     # Can't do types.either with multiple non-overlapping submodules, so define our own
     singleLdapValueType = lib.mkOptionType rec {
       name = "LDAP";
-      description = "LDAP value";
+      # TODO: It would be nice to define a { secret = ...; } option, using
+      # systemd's LoadCredentials for secrets. That would remove the last
+      # barrier to using DynamicUser for openldap. This is blocked on
+      # systemd/systemd#19604
+      description = ''
+        LDAP value - either a string, or an attrset containing
+        `path` or `base64` for included
+        values or base-64 encoded values respectively.
+      '';
       check = x: lib.isString x || (lib.isAttrs x && (x ? path || x ? base64));
       merge = lib.mergeEqualOption;
     };
@@ -24,7 +31,7 @@ let
         attrs = mkOption {
           type = types.attrsOf ldapValueType;
           default = {};
-          description = "Attributes of the parent entry.";
+          description = lib.mdDoc "Attributes of the parent entry.";
         };
         children = mkOption {
           # Hide the child attributes, to avoid infinite recursion in e.g. documentation
@@ -33,7 +40,7 @@ let
             hiddenOptions = lib.mapAttrs (name: attr: attr // { visible = false; }) options;
           in types.attrsOf (types.submodule { options = hiddenOptions; });
           default = {};
-          description = "Child entries of the current entry, with recursively the same structure.";
+          description = lib.mdDoc "Child entries of the current entry, with recursively the same structure.";
           example = lib.literalExpression ''
             {
                 "cn=schema" = {
@@ -52,7 +59,7 @@ let
         includes = mkOption {
           type = types.listOf types.path;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             LDIF files to include after the parent's attributes but before its children.
           '';
         };
@@ -76,59 +83,19 @@ let
     lib.flatten (lib.mapAttrsToList (name: value: attrsToLdif "${name},${dn}" value) children)
   );
 in {
-  imports = let
-    deprecationNote = "This option is removed due to the deprecation of `slapd.conf` upstream. Please migrate to `services.openldap.settings`, see the release notes for advice with this process.";
-    mkDatabaseOption = old: new:
-      lib.mkChangedOptionModule [ "services" "openldap" old ] [ "services" "openldap" "settings" "children" ]
-        (config: let
-          database = lib.getAttrFromPath [ "services" "openldap" "database" ] config;
-          value = lib.getAttrFromPath [ "services" "openldap" old ] config;
-        in lib.setAttrByPath ([ "olcDatabase={1}${database}" "attrs" ] ++ new) value);
-  in [
-    (lib.mkRemovedOptionModule [ "services" "openldap" "extraConfig" ] deprecationNote)
-    (lib.mkRemovedOptionModule [ "services" "openldap" "extraDatabaseConfig" ] deprecationNote)
-
-    (lib.mkChangedOptionModule [ "services" "openldap" "logLevel" ] [ "services" "openldap" "settings" "attrs" "olcLogLevel" ]
-      (config: lib.splitString " " (lib.getAttrFromPath [ "services" "openldap" "logLevel" ] config)))
-    (lib.mkChangedOptionModule [ "services" "openldap" "defaultSchemas" ] [ "services" "openldap" "settings" "children" "cn=schema" "includes"]
-      (config: lib.optionals (lib.getAttrFromPath [ "services" "openldap" "defaultSchemas" ] config) (
-        map (schema: "${openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ])))
-
-    (lib.mkChangedOptionModule [ "services" "openldap" "database" ] [ "services" "openldap" "settings" "children" ]
-      (config: let
-        database = lib.getAttrFromPath [ "services" "openldap" "database" ] config;
-      in {
-        "olcDatabase={1}${database}".attrs = {
-          # objectClass is case-insensitive, so don't need to capitalize ${database}
-          objectClass = [ "olcdatabaseconfig" "olc${database}config" ];
-          olcDatabase = "{1}${database}";
-          olcDbDirectory = lib.mkDefault "/var/db/openldap";
-        };
-        "cn=schema".includes = lib.mkDefault (
-          map (schema: "${openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ]
-        );
-      }))
-    (mkDatabaseOption "rootpwFile" [ "olcRootPW" "path" ])
-    (mkDatabaseOption "suffix" [ "olcSuffix" ])
-    (mkDatabaseOption "dataDir" [ "olcDbDirectory" ])
-    (mkDatabaseOption "rootdn" [ "olcRootDN" ])
-    (mkDatabaseOption "rootpw" [ "olcRootPW" ])
-  ];
   options = {
     services.openldap = {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "
-          Whether to enable the ldap server.
-        ";
+        description = lib.mdDoc "Whether to enable the ldap server.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.openldap;
         defaultText = literalExpression "pkgs.openldap";
-        description = ''
+        description = lib.mdDoc ''
           OpenLDAP package to use.
 
           This can be used to, for example, set an OpenLDAP package
@@ -140,25 +107,25 @@ in {
       user = mkOption {
         type = types.str;
         default = "openldap";
-        description = "User account under which slapd runs.";
+        description = lib.mdDoc "User account under which slapd runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "openldap";
-        description = "Group account under which slapd runs.";
+        description = lib.mdDoc "Group account under which slapd runs.";
       };
 
       urlList = mkOption {
         type = types.listOf types.str;
         default = [ "ldap:///" ];
-        description = "URL list slapd should listen on.";
+        description = lib.mdDoc "URL list slapd should listen on.";
         example = [ "ldaps:///" ];
       };
 
       settings = mkOption {
         type = ldapAttrsType;
-        description = "Configuration for OpenLDAP, in OLC format";
+        description = lib.mdDoc "Configuration for OpenLDAP, in OLC format";
         example = lib.literalExpression ''
           {
             attrs.olcLogLevel = [ "stats" ];
@@ -186,7 +153,7 @@ in {
                 attrs = {
                   objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
                   olcDatabase = "{1}mdb";
-                  olcDbDirectory = "/var/db/ldap";
+                  olcDbDirectory = "/var/lib/openldap/ldap";
                   olcDbIndex = [
                     "objectClass eq"
                     "cn pres,eq"
@@ -206,18 +173,28 @@ in {
       configDir = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Use this config directory instead of generating one from the
-          <literal>settings</literal> option. Overrides all NixOS settings. If
-          you use this option,ensure `olcPidFile` is set to `/run/slapd/slapd.conf`.
+          `settings` option. Overrides all NixOS settings.
+        '';
+        example = "/var/lib/openldap/slapd.d";
+      };
+
+      mutableConfig = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to allow writable on-line configuration. If
+          `true`, the NixOS settings will only be used to
+          initialize the OpenLDAP configuration if it does not exist, and are
+          subsequently ignored.
         '';
-        example = "/var/db/slapd.d";
       };
 
       declarativeContents = mkOption {
         type = with types; attrsOf lines;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Declarative contents for the LDAP database, in LDIF format by suffix.
 
           All data will be erased when starting the LDAP server. Modifications
@@ -225,6 +202,11 @@ in {
           reboot of the server. Performance-wise the database and indexes are
           rebuilt on each server startup, so this will slow down server startup,
           especially with large databases.
+
+          Note that the root of the DB must be defined in
+          `services.openldap.settings` and the
+          `olcDbDirectory` must begin with
+          `"/var/lib/openldap"`.
         '';
         example = lib.literalExpression ''
           {
@@ -245,13 +227,56 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ mic92 kwohlfahrt ];
+  meta.maintainers = with lib.maintainers; [ kwohlfahrt ];
+
+  config = let
+    dbSettings = mapAttrs' (name: { attrs, ... }: nameValuePair attrs.olcSuffix attrs)
+      (filterAttrs (name: { attrs, ... }: (hasPrefix "olcDatabase=" name) && attrs ? olcSuffix) cfg.settings.children);
+    settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings));
+    writeConfig = pkgs.writeShellScript "openldap-config" ''
+      set -euo pipefail
 
-  config = mkIf cfg.enable {
-    assertions = map (opt: {
-      assertion = ((getAttr opt cfg) != "_mkMergedOptionModule") -> (cfg.database != "_mkMergedOptionModule");
-      message = "Legacy OpenLDAP option `services.openldap.${opt}` requires `services.openldap.database` (use value \"mdb\" if unsure)";
-    }) legacyOptions;
+      ${lib.optionalString (!cfg.mutableConfig) ''
+        chmod -R u+w ${configDir}
+        rm -rf ${configDir}/*
+      ''}
+      if [ ! -e "${configDir}/cn=config.ldif" ]; then
+        ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile}
+      fi
+      chmod -R ${if cfg.mutableConfig then "u+rw" else "u+r-w"} ${configDir}
+    '';
+
+    contentsFiles = mapAttrs (dn: ldif: pkgs.writeText "${dn}.ldif" ldif) cfg.declarativeContents;
+    writeContents = pkgs.writeShellScript "openldap-load" ''
+      set -euo pipefail
+
+      rm -rf $2/*
+      ${openldap}/bin/slapadd -F ${configDir} -b $1 -l $3
+    '';
+  in mkIf cfg.enable {
+    assertions = [{
+      assertion = (cfg.declarativeContents != {}) -> cfg.configDir == null;
+      message = ''
+        Declarative DB contents (${attrNames cfg.declarativeContents}) are not
+        supported with user-managed configuration.
+      '';
+    }] ++ (map (dn: {
+      assertion = (getAttr dn dbSettings) ? "olcDbDirectory";
+      # olcDbDirectory is necessary to prepopulate database using `slapadd`.
+      message = ''
+        Declarative DB ${dn} does not exist in `services.openldap.settings`, or does not have
+        `olcDbDirectory` configured.
+      '';
+    }) (attrNames cfg.declarativeContents)) ++ (mapAttrsToList (dn: { olcDbDirectory ? null, ... }: {
+      # For forward compatibility with `DynamicUser`, and to avoid accidentally clobbering
+      # directories with `declarativeContents`.
+      assertion = (olcDbDirectory != null) ->
+      ((hasPrefix "/var/lib/openldap/" olcDbDirectory) && (olcDbDirectory != "/var/lib/openldap/"));
+      message = ''
+        Database ${dn} has `olcDbDirectory` (${olcDbDirectory}) that is not a subdirectory of
+        `/var/lib/openldap/`.
+      '';
+    }) dbSettings);
     environment.systemPackages = [ openldap ];
 
     # Literal attributes must always be set
@@ -259,7 +284,6 @@ in {
       attrs = {
         objectClass = "olcGlobal";
         cn = "config";
-        olcPidFile = "/run/slapd/slapd.pid";
       };
       children."cn=schema".attrs = {
         cn = "schema";
@@ -268,46 +292,39 @@ in {
     };
 
     systemd.services.openldap = {
-      description = "LDAP server";
+      description = "OpenLDAP Server Daemon";
+      documentation = [
+        "man:slapd"
+        "man:slapd-config"
+        "man:slapd-mdb"
+      ];
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-      preStart = let
-        settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings));
-
-        dbSettings = lib.filterAttrs (name: value: lib.hasPrefix "olcDatabase=" name) cfg.settings.children;
-        dataDirs = lib.mapAttrs' (name: value: lib.nameValuePair value.attrs.olcSuffix value.attrs.olcDbDirectory)
-          (lib.filterAttrs (_: value: value.attrs ? olcDbDirectory) dbSettings);
-        dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents;
-        mkLoadScript = dn: let
-          dataDir = lib.escapeShellArg (getAttr dn dataDirs);
-        in  ''
-          rm -rf ${dataDir}/*
-          ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${getAttr dn dataFiles}
-          chown -R "${cfg.user}:${cfg.group}" ${dataDir}
-        '';
-      in ''
-        mkdir -p /run/slapd
-        chown -R "${cfg.user}:${cfg.group}" /run/slapd
-
-        mkdir -p ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)}
-        chown "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)}
-
-        ${lib.optionalString (cfg.configDir == null) (''
-          rm -Rf ${configDir}/*
-          ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile}
-        '')}
-        chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir}
-
-        ${lib.concatStrings (map mkLoadScript (lib.attrNames cfg.declarativeContents))}
-        ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir}
-      '';
+      after = [ "network-online.target" ];
       serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStartPre = [
+          "!${pkgs.coreutils}/bin/mkdir -p ${configDir}"
+          "+${pkgs.coreutils}/bin/chown $USER ${configDir}"
+        ] ++ (lib.optional (cfg.configDir == null) writeConfig)
+        ++ (mapAttrsToList (dn: content: lib.escapeShellArgs [
+          writeContents dn (getAttr dn dbSettings).olcDbDirectory content
+        ]) contentsFiles)
+        ++ [ "${openldap}/bin/slaptest -u -F ${configDir}" ];
         ExecStart = lib.escapeShellArgs ([
-          "${openldap}/libexec/slapd" "-u" cfg.user "-g" cfg.group "-F" configDir
-          "-h" (lib.concatStringsSep " " cfg.urlList)
+          "${openldap}/libexec/slapd" "-d" "0" "-F" configDir "-h" (lib.concatStringsSep " " cfg.urlList)
         ]);
-        Type = "forking";
-        PIDFile = cfg.settings.attrs.olcPidFile;
+        Type = "notify";
+        # Fixes an error where openldap attempts to notify from a thread
+        # outside the main process:
+        #   Got notification message from PID 6378, but reception only permitted for main PID 6377
+        NotifyAccess = "all";
+        RuntimeDirectory = "openldap";
+        StateDirectory = ["openldap"]
+          ++ (map ({olcDbDirectory, ... }: removePrefix "/var/lib/" olcDbDirectory) (attrValues dbSettings));
+        StateDirectoryMode = "700";
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
       };
     };
 
diff --git a/nixos/modules/services/databases/opentsdb.nix b/nixos/modules/services/databases/opentsdb.nix
index e873b2f70115..288b716fce03 100644
--- a/nixos/modules/services/databases/opentsdb.nix
+++ b/nixos/modules/services/databases/opentsdb.nix
@@ -15,19 +15,13 @@ in {
 
     services.opentsdb = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to run OpenTSDB.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "OpenTSDB");
 
       package = mkOption {
         type = types.package;
         default = pkgs.opentsdb;
         defaultText = literalExpression "pkgs.opentsdb";
-        description = ''
+        description = lib.mdDoc ''
           OpenTSDB package to use.
         '';
       };
@@ -35,7 +29,7 @@ in {
       user = mkOption {
         type = types.str;
         default = "opentsdb";
-        description = ''
+        description = lib.mdDoc ''
           User account under which OpenTSDB runs.
         '';
       };
@@ -43,15 +37,15 @@ in {
       group = mkOption {
         type = types.str;
         default = "opentsdb";
-        description = ''
+        description = lib.mdDoc ''
           Group account under which OpenTSDB runs.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 4242;
-        description = ''
+        description = lib.mdDoc ''
           Which port OpenTSDB listens on.
         '';
       };
@@ -62,7 +56,7 @@ in {
           tsd.core.auto_create_metrics = true
           tsd.http.request.enable_chunked  = true
         '';
-        description = ''
+        description = lib.mdDoc ''
           The contents of OpenTSDB's configuration file
         '';
       };
diff --git a/nixos/modules/services/databases/pgmanage.nix b/nixos/modules/services/databases/pgmanage.nix
index f30f71866afd..cbf988d596f4 100644
--- a/nixos/modules/services/databases/pgmanage.nix
+++ b/nixos/modules/services/databases/pgmanage.nix
@@ -44,13 +44,13 @@ 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;
       default = pkgs.pgmanage;
       defaultText = literalExpression "pkgs.pgmanage";
-      description = ''
+      description = lib.mdDoc ''
         The pgmanage package to use.
       '';
     };
@@ -62,12 +62,12 @@ in {
         nuc-server  = "hostaddr=192.168.0.100 port=5432 dbname=postgres";
         mini-server = "hostaddr=127.0.0.1 port=5432 dbname=postgres sslmode=require";
       };
-      description = ''
+      description = lib.mdDoc ''
         pgmanage requires at least one PostgreSQL server be defined.
-        </para><para>
+
         Detailed information about PostgreSQL connection strings is available at:
-        <link xlink:href="http://www.postgresql.org/docs/current/static/libpq-connect.html"/>
-        </para><para>
+        <http://www.postgresql.org/docs/current/static/libpq-connect.html>
+
         Note that you should not specify your user name or password. That
         information will be entered on the login screen. If you specify a
         username or password, it will be removed by pgmanage before attempting to
@@ -78,16 +78,16 @@ in {
     allowCustomConnections = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         This tells pgmanage whether or not to allow anyone to use a custom
         connection from the login screen.
       '';
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8080;
-      description = ''
+      description = lib.mdDoc ''
         This tells pgmanage what port to listen on for browser requests.
       '';
     };
@@ -95,7 +95,7 @@ in {
     localOnly = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         This tells pgmanage whether or not to set the listening socket to local
         addresses only.
       '';
@@ -104,7 +104,7 @@ in {
     superOnly = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         This tells pgmanage whether or not to only allow super users to
         login. The recommended value is true and will restrict users who are not
         super users from logging in to any PostgreSQL instance through
@@ -116,7 +116,7 @@ in {
     loginGroup = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         This tells pgmanage to only allow users in a certain PostgreSQL group to
         login to pgmanage. Note that a connection will be made to PostgreSQL in
         order to test if the user is a member of the login group.
@@ -126,7 +126,7 @@ in {
     loginTimeout = mkOption {
       type = types.int;
       default = 3600;
-      description = ''
+      description = lib.mdDoc ''
         Number of seconds of inactivity before user is automatically logged
         out.
       '';
@@ -135,7 +135,7 @@ in {
     sqlRoot = mkOption {
       type = types.str;
       default = "/var/lib/pgmanage";
-      description = ''
+      description = lib.mdDoc ''
         This tells pgmanage where to put the SQL file history. All tabs are saved
         to this location so that if you get disconnected from pgmanage you
         don't lose your work.
@@ -147,16 +147,16 @@ in {
         options = {
           cert = mkOption {
             type = types.str;
-            description = "TLS certificate";
+            description = lib.mdDoc "TLS certificate";
           };
           key = mkOption {
             type = types.str;
-            description = "TLS key";
+            description = lib.mdDoc "TLS key";
           };
         };
       });
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         These options tell pgmanage where the TLS Certificate and Key files
         reside. If you use these options then you'll only be able to access
         pgmanage through a secure TLS connection. These options are only
@@ -165,14 +165,14 @@ in {
         configuration. This allows your web server to terminate the secure
         connection and pass on the request to pgmanage. You can find help to set
         up this configuration in:
-        <link xlink:href="https://github.com/pgManage/pgManage/blob/master/INSTALL_NGINX.md"/>
+        <https://github.com/pgManage/pgManage/blob/master/INSTALL_NGINX.md>
       '';
     };
 
     logLevel = mkOption {
       type = types.enum ["error" "warn" "notice" "info"];
       default = "error";
-      description = ''
+      description = lib.mdDoc ''
         Verbosity of logs
       '';
     };
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 2919022496a3..6665e7a088fc 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -40,20 +40,20 @@ in
 
     services.postgresql = {
 
-      enable = mkEnableOption "PostgreSQL Server";
+      enable = mkEnableOption (lib.mdDoc "PostgreSQL Server");
 
       package = mkOption {
         type = types.package;
         example = literalExpression "pkgs.postgresql_11";
-        description = ''
+        description = lib.mdDoc ''
           PostgreSQL package to use.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 5432;
-        description = ''
+        description = lib.mdDoc ''
           The port on which PostgreSQL listens.
         '';
       };
@@ -61,14 +61,14 @@ in
       checkConfig = mkOption {
         type = types.bool;
         default = true;
-        description = "Check the syntax of the configuration file at compile time";
+        description = lib.mdDoc "Check the syntax of the configuration file at compile time";
       };
 
       dataDir = mkOption {
         type = types.path;
         defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.postgresql.package.psqlSchema}"'';
         example = "/var/lib/postgresql/11";
-        description = ''
+        description = lib.mdDoc ''
           The data directory for PostgreSQL. If left as the default value
           this directory will automatically be created before the PostgreSQL server starts, otherwise
           the sysadmin is responsible for ensuring the directory exists with appropriate ownership
@@ -79,16 +79,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.
         '';
       };
@@ -96,7 +95,7 @@ in
       identMap = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Defines the mapping from system users to database users.
 
           The general form is:
@@ -109,8 +108,8 @@ in
         type = with types; listOf str;
         default = [];
         example = [ "--data-checksums" "--allow-group-access" ];
-        description = ''
-          Additional arguments passed to <literal>initdb</literal> during data dir
+        description = lib.mdDoc ''
+          Additional arguments passed to `initdb` during data dir
           initialisation.
         '';
       };
@@ -118,7 +117,7 @@ in
       initialScript = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A file containing SQL statements to execute on first startup.
         '';
       };
@@ -126,7 +125,7 @@ in
       ensureDatabases = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Ensures that the specified databases exist.
           This option will never delete existing databases, especially not when the value of this
           option is changed. This means that databases created once through this option or
@@ -143,14 +142,15 @@ in
           options = {
             name = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Name of the user to ensure.
               '';
             };
+
             ensurePermissions = mkOption {
               type = types.attrsOf types.str;
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 Permissions to ensure for the user, specified as an attribute set.
                 The attribute names specify the database and tables to grant the permissions for.
                 The attribute values specify the permissions to grant. You may specify one or
@@ -158,8 +158,8 @@ in
 
                 For more information on how to specify the target
                 and on which privileges exist, see the
-                <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>.
-                The attributes are used as <code>GRANT ''${attrValue} ON ''${attrName}</code>.
+                [GRANT syntax](https://www.postgresql.org/docs/current/sql-grant.html).
+                The attributes are used as `GRANT ''${attrValue} ON ''${attrName}`.
               '';
               example = literalExpression ''
                 {
@@ -168,10 +168,158 @@ 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 = [];
-        description = ''
+        description = lib.mdDoc ''
           Ensures that the specified users exist and have at least the ensured permissions.
           The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the
           same name only, and that without the need for a password.
@@ -200,7 +348,7 @@ in
       enableTCPIP = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether PostgreSQL should listen on all network interfaces.
           If disabled, the database can only be accessed via its Unix
           domain socket or via TCP connections to localhost.
@@ -211,9 +359,9 @@ in
         type = types.str;
         default = "[%p] ";
         example = "%m [%p] ";
-        description = ''
+        description = lib.mdDoc ''
           A printf-style string that is output at the beginning of each log line.
-          Upstream default is <literal>'%m [%p] '</literal>, i.e. it includes the timestamp. We do
+          Upstream default is `'%m [%p] '`, i.e. it includes the timestamp. We do
           not include the timestamp, because journal has it anyway.
         '';
       };
@@ -222,24 +370,24 @@ in
         type = types.listOf types.path;
         default = [];
         example = literalExpression "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
-        description = ''
+        description = lib.mdDoc ''
           List of PostgreSQL plugins. PostgreSQL version for each plugin should
-          match version for <literal>services.postgresql.package</literal> value.
+          match version for `services.postgresql.package` value.
         '';
       };
 
       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 ''
           {
@@ -255,8 +403,8 @@ in
       recoveryConfig = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
-          Contents of the <filename>recovery.conf</filename> file.
+        description = lib.mdDoc ''
+          Contents of the {file}`recovery.conf` file.
         '';
       };
 
@@ -265,7 +413,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.
         '';
@@ -295,7 +443,8 @@ in
       # Note: when changing the default, make it conditional on
       # ‘system.stateVersion’ to maintain compatibility with existing
       # systems!
-      mkDefault (if versionAtLeast config.system.stateVersion "21.11" then pkgs.postgresql_13
+      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");
@@ -380,12 +529,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 [
diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml
index 0ca9f3faed21..e48c578e6ce6 100644
--- a/nixos/modules/services/databases/postgresql.xml
+++ b/nixos/modules/services/databases/postgresql.xml
@@ -72,16 +72,20 @@ Type "help" for help.
 { config, pkgs, ... }:
 {
   <xref linkend="opt-environment.systemPackages" /> = [
-    (pkgs.writeScriptBin "upgrade-pg-cluster" ''
+    (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
 
-      # XXX replace `&lt;new version&gt;` with the psqlSchema here
-      export NEWDATA="/var/lib/postgresql/&lt;new version&gt;"
+      export NEWDATA="/var/lib/postgresql/${newPostgres.psqlSchema}"
 
-      # XXX specify the postgresql package you'd like to upgrade to
-      export NEWBIN="${pkgs.postgresql_13}/bin"
+      export NEWBIN="${newPostgres}/bin"
 
       export OLDDATA="${config.<xref linkend="opt-services.postgresql.dataDir"/>}"
       export OLDBIN="${config.<xref linkend="opt-services.postgresql.package"/>}/bin"
@@ -127,12 +131,25 @@ Type "help" for help.
    </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>):
+     After the upgrade it's advisable to analyze the new cluster.
+    </para>
+    <itemizedlist>
+     <listitem>
+      <para>
+       For PostgreSQL ≥ 14, use the <literal>vacuumdb</literal> command printed by the upgrades script.
+      </para>
+     </listitem>
+     <listitem>
+       <para>
+        For PostgreSQL &lt; 14, run (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>
+       </para>
+     </listitem>
+    </itemizedlist>
+    <para>
+      <warning><para>The next step removes the old state-directory!</para></warning>
 <programlisting>
 <prompt>$ </prompt>./delete_old_cluster.sh
 </programlisting>
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index a1bd73c9e371..1464f4487e39 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -58,25 +58,25 @@ in {
         type = types.package;
         default = pkgs.redis;
         defaultText = literalExpression "pkgs.redis";
-        description = "Which Redis derivation to use.";
+        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;
@@ -84,14 +84,14 @@ in {
               defaultText = literalExpression ''
                 if name == "" then "redis" else "redis-''${name}"
               '';
-              description = "The username and groupname for redis-server.";
+              description = lib.mdDoc "The username and groupname for redis-server.";
             };
 
             port = mkOption {
               type = types.port;
               default = if name == "" then 6379 else 0;
               defaultText = literalExpression ''if name == "" then 6379 else 0'';
-              description = ''
+              description = lib.mdDoc ''
                 The TCP port to accept connections.
                 If port 0 is specified Redis will not listen on a TCP socket.
               '';
@@ -100,17 +100,24 @@ in {
             openFirewall = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to open ports in the firewall for the server.
               '';
             };
 
+            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";
-              description = ''
+              description = lib.mdDoc ''
                 The IP interface to bind to.
-                <literal>null</literal> means "all interfaces".
+                `null` means "all interfaces".
               '';
               example = "192.0.2.1";
             };
@@ -121,13 +128,13 @@ in {
               defaultText = literalExpression ''
                 if name == "" then "/run/redis/redis.sock" else "/run/redis-''${name}/redis.sock"
               '';
-              description = "The path to the socket to bind to.";
+              description = lib.mdDoc "The path to the socket to bind to.";
             };
 
             unixSocketPerm = mkOption {
               type = types.int;
               default = 660;
-              description = "Change permissions for the socket";
+              description = lib.mdDoc "Change permissions for the socket";
               example = 600;
             };
 
@@ -135,38 +142,42 @@ in {
               type = types.str;
               default = "notice"; # debug, verbose, notice, warning
               example = "debug";
-              description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
+              description = lib.mdDoc "Specify the server verbosity level, options: debug, verbose, notice, warning.";
             };
 
             logfile = mkOption {
               type = types.str;
               default = "/dev/null";
-              description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
+              description = lib.mdDoc "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
               example = "/var/log/redis.log";
             };
 
             syslog = mkOption {
               type = types.bool;
               default = true;
-              description = "Enable logging to the system logger.";
+              description = lib.mdDoc "Enable logging to the system logger.";
             };
 
             databases = mkOption {
               type = types.int;
               default = 16;
-              description = "Set the number of databases.";
+              description = lib.mdDoc "Set the number of databases.";
             };
 
             maxclients = mkOption {
               type = types.int;
               default = 10000;
-              description = "Set the max number of connected clients at the same time.";
+              description = lib.mdDoc "Set the max number of connected clients at the same time.";
             };
 
             save = mkOption {
               type = with types; listOf (listOf int);
               default = [ [900 1] [300 10] [60 10000] ];
-              description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.";
+              description = mdDoc ''
+                The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.
+
+                If set to the empty list (`[]`) then RDB persistence will be disabled (useful if you are using AOF or don't want any persistence).
+              '';
             };
 
             slaveOf = mkOption {
@@ -174,27 +185,27 @@ in {
                 options = {
                   ip = mkOption {
                     type = str;
-                    description = "IP of the Redis master";
+                    description = lib.mdDoc "IP of the Redis master";
                     example = "192.168.1.100";
                   };
 
                   port = mkOption {
                     type = port;
-                    description = "port of the Redis master";
+                    description = lib.mdDoc "port of the Redis master";
                     default = 6379;
                   };
                 };
               }));
 
               default = null;
-              description = "IP and port to which this redis instance acts as a slave.";
+              description = lib.mdDoc "IP and port to which this redis instance acts as a slave.";
               example = { ip = "192.168.1.100"; port = 6379; };
             };
 
             masterAuth = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''If the master is password protected (using the requirePass configuration)
+              description = lib.mdDoc ''If the master is password protected (using the requirePass configuration)
               it is possible to tell the slave to authenticate before starting the replication synchronization
               process, otherwise the master will refuse the slave request.
               (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
@@ -203,7 +214,7 @@ in {
             requirePass = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
                 Use requirePassFile to store it outside of the nix store in a dedicated file.
               '';
@@ -213,42 +224,42 @@ in {
             requirePassFile = mkOption {
               type = with types; nullOr path;
               default = null;
-              description = "File with password for the database.";
+              description = lib.mdDoc "File with password for the database.";
               example = "/run/keys/redis-password";
             };
 
             appendOnly = mkOption {
               type = types.bool;
               default = false;
-              description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
+              description = lib.mdDoc "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
             };
 
             appendFsync = mkOption {
               type = types.str;
               default = "everysec"; # no, always, everysec
-              description = "How often to fsync the append-only log, options: no, always, everysec.";
+              description = lib.mdDoc "How often to fsync the append-only log, options: no, always, everysec.";
             };
 
             slowLogLogSlowerThan = mkOption {
               type = types.int;
               default = 10000;
-              description = "Log queries whose execution take longer than X in milliseconds.";
+              description = lib.mdDoc "Log queries whose execution take longer than X in milliseconds.";
               example = 1000;
             };
 
             slowLogMaxLen = mkOption {
               type = types.int;
               default = 128;
-              description = "Maximum number of items to keep in slow log.";
+              description = lib.mdDoc "Maximum number of items to keep in slow log.";
             };
 
             settings = mkOption {
               # TODO: this should be converted to freeformType
               type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 Redis configuration. Refer to
-                <link xlink:href="https://redis.io/topics/config"/>
+                <https://redis.io/topics/config>
                 for details on supported values.
               '';
               example = literalExpression ''
@@ -260,23 +271,23 @@ 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 = map (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}") config.save;
+              save = if config.save == []
+                then ''""'' # Disable saving with `save = ""`
+                else map
+                  (d: "${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}")
+                  config.save;
               dbfilename = "dump.rdb";
               dir = "/var/lib/${redisName name}";
-              appendOnly = config.appendOnly;
               appendfsync = config.appendFsync;
               slowlog-log-slower-than = config.slowLogLogSlowerThan;
               slowlog-max-len = config.slowLogMaxLen;
             }
-            (mkIf (config.bind != null) { bind = config.bind; })
+            (mkIf (config.bind != null) { inherit (config) bind; })
             (mkIf (config.unixSocket != null) {
               unixsocket = config.unixSocket;
               unixsocketperm = toString config.unixSocketPerm;
@@ -286,7 +297,7 @@ in {
             (mkIf (config.requirePass != null) { requirepass = config.requirePass; })
           ];
         }));
-        description = "Configuration of multiple <literal>redis-server</literal> instances.";
+        description = lib.mdDoc "Configuration of multiple `redis-server` instances.";
         default = {};
       };
     };
@@ -332,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/nixos/modules/services/databases/rethinkdb.nix b/nixos/modules/services/databases/rethinkdb.nix
index c764d6c21c6c..f5391b48e89c 100644
--- a/nixos/modules/services/databases/rethinkdb.nix
+++ b/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;
@@ -24,22 +24,22 @@ in
 
       user = mkOption {
         default = "rethinkdb";
-        description = "User account under which RethinkDB runs.";
+        description = lib.mdDoc "User account under which RethinkDB runs.";
       };
 
       group = mkOption {
         default = "rethinkdb";
-        description = "Group which rethinkdb user belongs to.";
+        description = lib.mdDoc "Group which rethinkdb user belongs to.";
       };
 
       dbpath = mkOption {
         default = "/var/db/rethinkdb";
-        description = "Location where RethinkDB stores its data, 1 data directory per instance.";
+        description = lib.mdDoc "Location where RethinkDB stores its data, 1 data directory per instance.";
       };
 
       pidpath = mkOption {
         default = "/run/rethinkdb";
-        description = "Location where each instance's pid file is located.";
+        description = lib.mdDoc "Location where each instance's pid file is located.";
       };
 
       #cfgpath = mkOption {
diff --git a/nixos/modules/services/databases/riak.nix b/nixos/modules/services/databases/riak.nix
deleted file mode 100644
index cc4237d038cd..000000000000
--- a/nixos/modules/services/databases/riak.nix
+++ /dev/null
@@ -1,162 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.riak;
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.riak = {
-
-      enable = mkEnableOption "riak";
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.riak;
-        defaultText = literalExpression "pkgs.riak";
-        description = ''
-          Riak package to use.
-        '';
-      };
-
-      nodeName = mkOption {
-        type = types.str;
-        default = "riak@127.0.0.1";
-        description = ''
-          Name of the Erlang node.
-        '';
-      };
-
-      distributedCookie = mkOption {
-        type = types.str;
-        default = "riak";
-        description = ''
-          Cookie for distributed node communication.  All nodes in the
-          same cluster should use the same cookie or they will not be able to
-          communicate.
-        '';
-      };
-
-      dataDir = mkOption {
-        type = types.path;
-        default = "/var/db/riak";
-        description = ''
-          Data directory for Riak.
-        '';
-      };
-
-      logDir = mkOption {
-        type = types.path;
-        default = "/var/log/riak";
-        description = ''
-          Log directory for Riak.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Additional text to be appended to <filename>riak.conf</filename>.
-        '';
-      };
-
-      extraAdvancedConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Additional text to be appended to <filename>advanced.config</filename>.
-        '';
-      };
-
-    };
-
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    environment.systemPackages = [ cfg.package ];
-    environment.etc."riak/riak.conf".text = ''
-      nodename = ${cfg.nodeName}
-      distributed_cookie = ${cfg.distributedCookie}
-
-      platform_log_dir = ${cfg.logDir}
-      platform_etc_dir = /etc/riak
-      platform_data_dir = ${cfg.dataDir}
-
-      ${cfg.extraConfig}
-    '';
-
-    environment.etc."riak/advanced.config".text = ''
-      ${cfg.extraAdvancedConfig}
-    '';
-
-    users.users.riak = {
-      name = "riak";
-      uid = config.ids.uids.riak;
-      group = "riak";
-      description = "Riak server user";
-    };
-
-    users.groups.riak.gid = config.ids.gids.riak;
-
-    systemd.services.riak = {
-      description = "Riak Server";
-
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-
-      path = [
-        pkgs.util-linux # for `logger`
-        pkgs.bash
-      ];
-
-      environment.HOME = "${cfg.dataDir}";
-      environment.RIAK_DATA_DIR = "${cfg.dataDir}";
-      environment.RIAK_LOG_DIR = "${cfg.logDir}";
-      environment.RIAK_ETC_DIR = "/etc/riak";
-
-      preStart = ''
-        if ! test -e ${cfg.logDir}; then
-          mkdir -m 0755 -p ${cfg.logDir}
-          chown -R riak ${cfg.logDir}
-        fi
-
-        if ! test -e ${cfg.dataDir}; then
-          mkdir -m 0700 -p ${cfg.dataDir}
-          chown -R riak ${cfg.dataDir}
-        fi
-      '';
-
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/riak console";
-        ExecStop = "${cfg.package}/bin/riak stop";
-        StandardInput = "tty";
-        User = "riak";
-        Group = "riak";
-        PermissionsStartOnly = true;
-        # Give Riak a decent amount of time to clean up.
-        TimeoutStopSec = 120;
-        LimitNOFILE = 65536;
-      };
-
-      unitConfig.RequiresMountsFor = [
-        "${cfg.dataDir}"
-        "${cfg.logDir}"
-        "/etc/riak"
-      ];
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/databases/surrealdb.nix b/nixos/modules/services/databases/surrealdb.nix
new file mode 100644
index 000000000000..050a5336cb4c
--- /dev/null
+++ b/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/nixos/modules/services/databases/victoriametrics.nix b/nixos/modules/services/databases/victoriametrics.nix
index 0513dcff172b..638066a42dbd 100644
--- a/nixos/modules/services/databases/victoriametrics.nix
+++ b/nixos/modules/services/databases/victoriametrics.nix
@@ -2,36 +2,36 @@
 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;
       defaultText = literalExpression "pkgs.victoriametrics";
-      description = ''
+      description = lib.mdDoc ''
         The VictoriaMetrics distribution to use.
       '';
     };
     listenAddress = mkOption {
       default = ":8428";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The listen address for the http interface.
       '';
     };
     retentionPeriod = mkOption {
       type = types.int;
       default = 1;
-      description = ''
+      description = lib.mdDoc ''
         Retention period in months.
       '';
     };
     extraOptions = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
-        Extra options to pass to VictoriaMetrics. See the README: <link
-        xlink:href="https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md" />
-        or <command>victoriametrics -help</command> for more
+      description = lib.mdDoc ''
+        Extra options to pass to VictoriaMetrics. See the README:
+        <https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md>
+        or {command}`victoriametrics -help` for more
         information.
       '';
     };
diff --git a/nixos/modules/services/desktops/accountsservice.nix b/nixos/modules/services/desktops/accountsservice.nix
index ae2ecb5ffeb7..af62850acdc1 100644
--- a/nixos/modules/services/desktops/accountsservice.nix
+++ b/nixos/modules/services/desktops/accountsservice.nix
@@ -19,7 +19,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable AccountsService, a DBus service for accessing
           the list of user accounts and information attached to those accounts.
         '';
diff --git a/nixos/modules/services/desktops/bamf.nix b/nixos/modules/services/desktops/bamf.nix
index 13de3a44328f..3e40a7055348 100644
--- a/nixos/modules/services/desktops/bamf.nix
+++ b/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/nixos/modules/services/desktops/blueman.nix b/nixos/modules/services/desktops/blueman.nix
index 18ad610247ed..fad2f21bce5b 100644
--- a/nixos/modules/services/desktops/blueman.nix
+++ b/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/nixos/modules/services/desktops/cpupower-gui.nix b/nixos/modules/services/desktops/cpupower-gui.nix
index f66afc0a3dc1..47071aebce8d 100644
--- a/nixos/modules/services/desktops/cpupower-gui.nix
+++ b/nixos/modules/services/desktops/cpupower-gui.nix
@@ -11,7 +11,7 @@ in {
         type = lib.types.bool;
         default = false;
         example = true;
-        description = ''
+        description = lib.mdDoc ''
           Enables dbus/systemd service needed by cpupower-gui.
           These services are responsible for retrieving and modifying cpu power
           saving settings.
diff --git a/nixos/modules/services/desktops/dleyna-renderer.nix b/nixos/modules/services/desktops/dleyna-renderer.nix
index 7f88605f627c..daf65180b36f 100644
--- a/nixos/modules/services/desktops/dleyna-renderer.nix
+++ b/nixos/modules/services/desktops/dleyna-renderer.nix
@@ -10,7 +10,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable dleyna-renderer service, a DBus service
           for handling DLNA renderers.
         '';
diff --git a/nixos/modules/services/desktops/dleyna-server.nix b/nixos/modules/services/desktops/dleyna-server.nix
index 9a131a5e700f..9cbcd2a9cdae 100644
--- a/nixos/modules/services/desktops/dleyna-server.nix
+++ b/nixos/modules/services/desktops/dleyna-server.nix
@@ -10,7 +10,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable dleyna-server service, a DBus service
           for handling DLNA servers.
         '';
diff --git a/nixos/modules/services/desktops/espanso.nix b/nixos/modules/services/desktops/espanso.nix
index 4ef6724dda0a..cbc48034795e 100644
--- a/nixos/modules/services/desktops/espanso.nix
+++ b/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/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index 5fecc64b4f70..3b14ad75ab30 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -14,7 +14,7 @@ in {
   ###### interface
   options = {
     services.flatpak = {
-      enable = mkEnableOption "flatpak";
+      enable = mkEnableOption (lib.mdDoc "flatpak");
     };
   };
 
diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index 60a34dd65631..b04f46c26a56 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -16,19 +16,19 @@ let
     options = {
       desktopID = mkOption {
         type = types.str;
-        description = "Desktop ID of the application.";
+        description = lib.mdDoc "Desktop ID of the application.";
       };
 
       isAllowed = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether the application will be allowed access to location information.
         '';
       };
 
       isSystem = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether the application is a system component or not.
         '';
       };
@@ -36,7 +36,7 @@ let
       users = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of UIDs of all users for which this application is allowed location
           info access, Defaults to an empty string to allow it for all users.
         '';
@@ -67,7 +67,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GeoClue 2 daemon, a DBus service
           that provides location information for accessing.
         '';
@@ -76,7 +76,7 @@ in
       enableDemoAgent = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use the GeoClue demo agent. This should be
           overridden by desktop environments that provide their own
           agent.
@@ -86,7 +86,7 @@ in
       enableNmea = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to fetch location from NMEA sources on local network.
         '';
       };
@@ -94,7 +94,7 @@ in
       enable3G = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable 3G source.
         '';
       };
@@ -102,7 +102,7 @@ in
       enableCDMA = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable CDMA source.
         '';
       };
@@ -110,7 +110,7 @@ in
       enableModemGPS = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Modem-GPS source.
         '';
       };
@@ -118,7 +118,7 @@ in
       enableWifi = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable WiFi source.
         '';
       };
@@ -127,7 +127,7 @@ in
         type = types.str;
         default = "https://location.services.mozilla.com/v1/geolocate?key=geoclue";
         example = "https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_KEY";
-        description = ''
+        description = lib.mdDoc ''
           The url to the wifi GeoLocation Service.
         '';
       };
@@ -135,7 +135,7 @@ in
       submitData = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to submit data to a GeoLocation Service.
         '';
       };
@@ -143,7 +143,7 @@ in
       submissionUrl = mkOption {
         type = types.str;
         default = "https://location.services.mozilla.com/v1/submit?key=geoclue";
-        description = ''
+        description = lib.mdDoc ''
           The url to submit data to a GeoLocation Service.
         '';
       };
@@ -151,7 +151,7 @@ in
       submissionNick = mkOption {
         type = types.str;
         default = "geoclue";
-        description = ''
+        description = lib.mdDoc ''
           A nickname to submit network data with.
           Must be 2-32 characters long.
         '';
@@ -167,7 +167,7 @@ in
             users = [ "300" ];
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Specify extra settings per application.
         '';
       };
@@ -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
@@ -216,6 +217,7 @@ in
         # we can't be part of a system service, and the agent should
         # be okay with the main service coming and going
         wantedBy = [ "default.target" ];
+        after = lib.optionals cfg.enableWifi [ "network-online.target" ];
         unitConfig.ConditionUser = "!@system";
         serviceConfig = {
           Type = "exec";
diff --git a/nixos/modules/services/desktops/gnome/at-spi2-core.nix b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
index 1268a9d49b82..10a2f1f9eca0 100644
--- a/nixos/modules/services/desktops/gnome/at-spi2-core.nix
+++ b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
@@ -27,12 +27,12 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable at-spi2-core, a service for the Assistive Technologies
           available on the GNOME platform.
 
           Enable this if you get the error or warning
-          <literal>The name org.a11y.Bus was not provided by any .service files</literal>.
+          `The name org.a11y.Bus was not provided by any .service files`.
         '';
       };
 
@@ -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/nixos/modules/services/desktops/gnome/chrome-gnome-shell.nix b/nixos/modules/services/desktops/gnome/chrome-gnome-shell.nix
deleted file mode 100644
index 15c5bfbd8210..000000000000
--- a/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/nixos/modules/services/desktops/gnome/evolution-data-server.nix b/nixos/modules/services/desktops/gnome/evolution-data-server.nix
index bd2242d98182..0006ba1a7bad 100644
--- a/nixos/modules/services/desktops/gnome/evolution-data-server.nix
+++ b/nixos/modules/services/desktops/gnome/evolution-data-server.nix
@@ -27,20 +27,20 @@ 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 = [ ];
-        description = "Plugins for Evolution Data Server.";
+        description = lib.mdDoc "Plugins for Evolution Data Server.";
       };
     };
     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 = [ ];
         example = literalExpression "[ pkgs.evolution-ews ]";
-        description = "Plugins for Evolution.";
+        description = lib.mdDoc "Plugins for Evolution.";
       };
 
     };
diff --git a/nixos/modules/services/desktops/gnome/glib-networking.nix b/nixos/modules/services/desktops/gnome/glib-networking.nix
index 1039605391ab..6b54f46f0cf5 100644
--- a/nixos/modules/services/desktops/gnome/glib-networking.nix
+++ b/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/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix b/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
new file mode 100644
index 000000000000..5d4ddce94220
--- /dev/null
+++ b/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/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix b/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
index 9e9771cf5415..f24e6f1eb155 100644
--- a/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
+++ b/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/nixos/modules/services/desktops/gnome/gnome-keyring.nix b/nixos/modules/services/desktops/gnome/gnome-keyring.nix
index d821da164beb..6c7e713b32d5 100644
--- a/nixos/modules/services/desktops/gnome/gnome-keyring.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-keyring.nix
@@ -27,7 +27,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GNOME Keyring daemon, a service designed to
           take care of the user's security credentials,
           such as user names and passwords.
diff --git a/nixos/modules/services/desktops/gnome/gnome-online-accounts.nix b/nixos/modules/services/desktops/gnome/gnome-online-accounts.nix
index 01f7e3695cf0..ed5e000cae3e 100644
--- a/nixos/modules/services/desktops/gnome/gnome-online-accounts.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-online-accounts.nix
@@ -27,7 +27,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GNOME Online Accounts daemon, a service that provides
           a single sign-on framework for the GNOME desktop.
         '';
diff --git a/nixos/modules/services/desktops/gnome/gnome-online-miners.nix b/nixos/modules/services/desktops/gnome/gnome-online-miners.nix
index 5f9039f68c4e..7cf1bfa1b046 100644
--- a/nixos/modules/services/desktops/gnome/gnome-online-miners.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-online-miners.nix
@@ -27,7 +27,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GNOME Online Miners, a service that
           crawls through your online content.
         '';
diff --git a/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix b/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix
index b5573d2fc21b..0a5b67eb2722 100644
--- a/nixos/modules/services/desktops/gnome/gnome-remote-desktop.nix
+++ b/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/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix b/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix
index 9c68c9b76e9e..ca739b06a5a5 100644
--- a/nixos/modules/services/desktops/gnome/gnome-settings-daemon.nix
+++ b/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/nixos/modules/services/desktops/gnome/gnome-user-share.nix b/nixos/modules/services/desktops/gnome/gnome-user-share.nix
index 38256af309cc..0c88d13b343d 100644
--- a/nixos/modules/services/desktops/gnome/gnome-user-share.nix
+++ b/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/nixos/modules/services/desktops/gnome/rygel.nix b/nixos/modules/services/desktops/gnome/rygel.nix
index 7ea9778fc408..9c0faaa4885b 100644
--- a/nixos/modules/services/desktops/gnome/rygel.nix
+++ b/nixos/modules/services/desktops/gnome/rygel.nix
@@ -21,10 +21,10 @@ with lib;
     services.gnome.rygel = {
       enable = mkOption {
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Rygel UPnP Mediaserver.
 
-          You will need to also allow UPnP connections in firewall, see the following <link xlink:href="https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795">comment</link>.
+          You will need to also allow UPnP connections in firewall, see the following [comment](https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795).
         '';
         type = types.bool;
       };
diff --git a/nixos/modules/services/desktops/gnome/sushi.nix b/nixos/modules/services/desktops/gnome/sushi.nix
index 3133a3a0d985..446851f434d8 100644
--- a/nixos/modules/services/desktops/gnome/sushi.nix
+++ b/nixos/modules/services/desktops/gnome/sushi.nix
@@ -27,7 +27,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Sushi, a quick previewer for nautilus.
         '';
       };
diff --git a/nixos/modules/services/desktops/gnome/tracker-miners.nix b/nixos/modules/services/desktops/gnome/tracker-miners.nix
index 9351007d30b5..a3c58f374208 100644
--- a/nixos/modules/services/desktops/gnome/tracker-miners.nix
+++ b/nixos/modules/services/desktops/gnome/tracker-miners.nix
@@ -27,7 +27,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Tracker miners, indexing services for Tracker
           search engine and metadata storage system.
         '';
diff --git a/nixos/modules/services/desktops/gnome/tracker.nix b/nixos/modules/services/desktops/gnome/tracker.nix
index fef399d0112e..e6404c84a26f 100644
--- a/nixos/modules/services/desktops/gnome/tracker.nix
+++ b/nixos/modules/services/desktops/gnome/tracker.nix
@@ -30,7 +30,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Tracker services, a search engine,
           search tool and metadata storage system.
         '';
@@ -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/nixos/modules/services/desktops/gsignond.nix b/nixos/modules/services/desktops/gsignond.nix
index 465acd73fa64..cf80fd75452b 100644
--- a/nixos/modules/services/desktops/gsignond.nix
+++ b/nixos/modules/services/desktops/gsignond.nix
@@ -20,7 +20,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable gSignOn daemon, a DBus service
           which performs user authentication on behalf of its clients.
         '';
@@ -29,7 +29,7 @@ in
       plugins = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           What plugins to use with the gSignOn daemon.
         '';
       };
diff --git a/nixos/modules/services/desktops/gvfs.nix b/nixos/modules/services/desktops/gvfs.nix
index 1aa64ea37db5..7e15b433fcc2 100644
--- a/nixos/modules/services/desktops/gvfs.nix
+++ b/nixos/modules/services/desktops/gvfs.nix
@@ -29,14 +29,14 @@ 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 {
         type = types.package;
         default = pkgs.gnome.gvfs;
         defaultText = literalExpression "pkgs.gnome.gvfs";
-        description = "Which GVfs package to use.";
+        description = lib.mdDoc "Which GVfs package to use.";
       };
 
     };
@@ -56,6 +56,8 @@ in
 
     services.udev.packages = [ pkgs.libmtp.out ];
 
+    services.udisks2.enable = true;
+
     # Needed for unwrapped applications
     environment.sessionVariables.GIO_EXTRA_MODULES = [ "${cfg.package}/lib/gio/modules" ];
 
diff --git a/nixos/modules/services/desktops/malcontent.nix b/nixos/modules/services/desktops/malcontent.nix
index 1fbeb17e6aeb..27b4577f4c2a 100644
--- a/nixos/modules/services/desktops/malcontent.nix
+++ b/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/nixos/modules/services/desktops/neard.nix b/nixos/modules/services/desktops/neard.nix
index 9b0f8d1b3a77..9130b8d3d216 100644
--- a/nixos/modules/services/desktops/neard.nix
+++ b/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/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json b/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json
new file mode 100644
index 000000000000..689fca88359b
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json
@@ -0,0 +1,28 @@
+{
+  "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-adapter"
+    }
+  ]
+}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json b/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
index c7f58fd5799a..0f1ebe5749c6 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json
@@ -91,6 +91,7 @@
         "adapter.auto-port-config": {
           "mode": "dsp",
           "monitor": false,
+          "control": false,
           "position": "unknown"
         }
       }
@@ -109,6 +110,7 @@
         "adapter.auto-port-config": {
           "mode": "dsp",
           "monitor": false,
+          "control": false,
           "position": "unknown"
         }
       }
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json
new file mode 100644
index 000000000000..4f669895d87b
--- /dev/null
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json
@@ -0,0 +1,38 @@
+{
+  "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-avb",
+      "args": {}
+    }
+  ],
+  "context.exec": [],
+  "stream.properties": {},
+  "avb.properties": {
+    "ifname": "enp3s0",
+    "vm.overrides": {}
+  }
+}
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
index b19fb33ec178..114afbfb0ea4 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
@@ -62,6 +62,9 @@
           "application.process.binary": "teams"
         },
         {
+          "application.process.binary": "teams-insiders"
+        },
+        {
           "application.process.binary": "skypeforlinux"
         }
       ],
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
index 7c79f0168c02..bf3b2d660827 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json
@@ -10,6 +10,7 @@
   },
   "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",
diff --git a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
index 09761d6300e8..203139294c6b 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
@@ -39,14 +39,14 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the deprecated example Pipewire session manager";
+        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 = ''
+        description = lib.mdDoc ''
           The pipewire-media-session derivation to use.
         '';
       };
@@ -54,7 +54,7 @@ in {
       config = {
         media-session = mkOption {
           type = json.type;
-          description = ''
+          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
           '';
@@ -63,7 +63,7 @@ in {
 
         alsa-monitor = mkOption {
           type = json.type;
-          description = ''
+          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
           '';
@@ -72,7 +72,7 @@ in {
 
         bluez-monitor = mkOption {
           type = json.type;
-          description = ''
+          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
           '';
@@ -81,7 +81,7 @@ in {
 
         v4l2-monitor = mkOption {
           type = json.type;
-          description = ''
+          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
           '';
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index 1323336d866e..a4ef88a45ad0 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -50,13 +50,13 @@ in {
   ###### interface
   options = {
     services.pipewire = {
-      enable = mkEnableOption "pipewire service";
+      enable = mkEnableOption (lib.mdDoc "pipewire service");
 
       package = mkOption {
         type = types.package;
         default = pkgs.pipewire;
         defaultText = literalExpression "pkgs.pipewire";
-        description = ''
+        description = lib.mdDoc ''
           The pipewire derivation to use.
         '';
       };
@@ -64,7 +64,7 @@ in {
       socketActivation = mkOption {
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Automatically run pipewire when connections are made to the pipewire socket.
         '';
       };
@@ -73,7 +73,7 @@ in {
         client = mkOption {
           type = json.type;
           default = {};
-          description = ''
+          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
           '';
@@ -82,7 +82,7 @@ in {
         client-rt = mkOption {
           type = json.type;
           default = {};
-          description = ''
+          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
           '';
@@ -91,7 +91,7 @@ in {
         jack = mkOption {
           type = json.type;
           default = {};
-          description = ''
+          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
           '';
@@ -100,7 +100,7 @@ in {
         pipewire = mkOption {
           type = json.type;
           default = {};
-          description = ''
+          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
           '';
@@ -109,7 +109,7 @@ in {
         pipewire-pulse = mkOption {
           type = json.type;
           default = {};
-          description = ''
+          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
           '';
@@ -122,27 +122,27 @@ in {
           # this is for backwards compatibility
           default = cfg.alsa.enable || cfg.jack.enable || cfg.pulse.enable;
           defaultText = lib.literalExpression "config.services.pipewire.alsa.enable || config.services.pipewire.jack.enable || config.services.pipewire.pulse.enable";
-          description = "Whether to use PipeWire as the primary sound server";
+          description = lib.mdDoc "Whether to use PipeWire as the primary sound server";
         };
       };
 
       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 {
         type = lib.types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If true, a system-wide PipeWire service and socket is enabled
           allowing all users in the "pipewire" group to use it simultaneously.
           If false, then user units are used instead, restricting access to
@@ -234,12 +234,12 @@ in {
     environment.etc."pipewire/pipewire.conf" = {
       source = json.generate "pipewire.conf" configs.pipewire;
     };
-    environment.etc."pipewire/pipewire-pulse.conf" = {
+    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.optional cfg.jack.enable "${cfg.package.jack}/lib";
+      lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
 
     users = lib.mkIf cfg.systemWide {
       users.pipewire = {
@@ -251,6 +251,8 @@ in {
         ] ++ lib.optional config.security.rtkit.enable "rtkit";
         description = "Pipewire system service user";
         isSystemUser = true;
+        home = "/var/lib/pipewire";
+        createHome = true;
       };
       groups.pipewire.gid = config.ids.gids.pipewire;
     };
@@ -258,5 +260,8 @@ in {
     # 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/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix
index 1dbdd842c4a1..4b36b99aa7c1 100644
--- a/nixos/modules/services/desktops/pipewire/wireplumber.nix
+++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix
@@ -14,14 +14,14 @@ in
         type = lib.types.bool;
         default = config.services.pipewire.enable;
         defaultText = lib.literalExpression "config.services.pipewire.enable";
-        description = "Whether to enable Wireplumber, a modular session / policy manager for PipeWire";
+        description = lib.mdDoc "Whether to enable Wireplumber, a modular session / policy manager for PipeWire";
       };
 
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.wireplumber;
         defaultText = lib.literalExpression "pkgs.wireplumber";
-        description = "The wireplumber derivation to use.";
+        description = lib.mdDoc "The wireplumber derivation to use.";
       };
     };
   };
@@ -32,16 +32,34 @@ in
         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";
+      }
     ];
 
     environment.systemPackages = [ cfg.package ];
 
     environment.etc."wireplumber/main.lua.d/80-nixos.lua" = lib.mkIf (!pwUsedForAudio) {
-     text = ''
+      text = ''
         -- Pipewire is not used for audio, so prevent it from grabbing audio devices
         alsa_monitor.enable = function() end
       '';
     };
+    environment.etc."wireplumber/main.lua.d/80-systemwide.lua" = lib.mkIf config.services.pipewire.systemWide {
+      text = ''
+        -- When running system-wide, these settings need to be disabled (they
+        -- use functions that aren't available on the system dbus).
+        alsa_monitor.properties["alsa.reserve"] = false
+        default_access.properties["enable-flatpak-portal"] = false
+      '';
+    };
+    environment.etc."wireplumber/bluetooth.lua.d/80-systemwide.lua" = lib.mkIf config.services.pipewire.systemWide {
+      text = ''
+        -- When running system-wide, logind-integration needs to be disabled.
+        bluez_monitor.properties["with-logind"] = false
+      '';
+    };
 
     systemd.packages = [ cfg.package ];
 
@@ -50,5 +68,10 @@ in
 
     systemd.services.wireplumber.wantedBy = [ "pipewire.service" ];
     systemd.user.services.wireplumber.wantedBy = [ "pipewire.service" ];
+
+    systemd.services.wireplumber.environment = lib.mkIf config.services.pipewire.systemWide {
+      # Force wireplumber to use system dbus.
+      DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/dbus/system_bus_socket";
+    };
   };
 }
diff --git a/nixos/modules/services/desktops/profile-sync-daemon.nix b/nixos/modules/services/desktops/profile-sync-daemon.nix
index 6206295272fc..e307c6735004 100644
--- a/nixos/modules/services/desktops/profile-sync-daemon.nix
+++ b/nixos/modules/services/desktops/profile-sync-daemon.nix
@@ -9,7 +9,7 @@ in {
     enable = mkOption {
       type = bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the Profile Sync daemon.
       '';
     };
@@ -17,7 +17,7 @@ in {
       type = str;
       default = "1h";
       example = "1h 30min";
-      description = ''
+      description = lib.mdDoc ''
         The amount of time to wait before syncing browser profiles back to the
         disk.
 
diff --git a/nixos/modules/services/desktops/system-config-printer.nix b/nixos/modules/services/desktops/system-config-printer.nix
index 09c68c587b43..caebfabf146c 100644
--- a/nixos/modules/services/desktops/system-config-printer.nix
+++ b/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");
 
     };
 
@@ -34,7 +34,8 @@ with lib;
     ];
 
     # for $out/bin/install-printer-driver
-    services.packagekit.enable = true;
+    # TODO: Enable once #177946 is resolved
+    # services.packagekit.enable = true;
 
   };
 
diff --git a/nixos/modules/services/desktops/telepathy.nix b/nixos/modules/services/desktops/telepathy.nix
index b5f6a5fcbcfd..cdc6eb26de7e 100644
--- a/nixos/modules/services/desktops/telepathy.nix
+++ b/nixos/modules/services/desktops/telepathy.nix
@@ -19,7 +19,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Telepathy service, a communications framework
           that enables real-time communication via pluggable protocol backends.
         '';
diff --git a/nixos/modules/services/desktops/tumbler.nix b/nixos/modules/services/desktops/tumbler.nix
index f5341df2f7a4..203071ec660d 100644
--- a/nixos/modules/services/desktops/tumbler.nix
+++ b/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/nixos/modules/services/desktops/zeitgeist.nix b/nixos/modules/services/desktops/zeitgeist.nix
index 297fd1d3ff2f..0eb2a4c9c371 100644
--- a/nixos/modules/services/desktops/zeitgeist.nix
+++ b/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/nixos/modules/services/development/blackfire.nix b/nixos/modules/services/development/blackfire.nix
index 8564aabc6a37..054cef9ae80b 100644
--- a/nixos/modules/services/development/blackfire.nix
+++ b/nixos/modules/services/development/blackfire.nix
@@ -16,9 +16,9 @@ in {
 
   options = {
     services.blackfire-agent = {
-      enable = lib.mkEnableOption "Blackfire profiler agent";
+      enable = lib.mkEnableOption (lib.mdDoc "Blackfire profiler agent");
       settings = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           See https://blackfire.io/docs/up-and-running/configuration/agent
         '';
         type = lib.types.submodule {
@@ -27,7 +27,7 @@ in {
           options = {
             server-id = lib.mkOption {
               type = lib.types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Sets the server id used to authenticate with Blackfire
 
                 You can find your personal server-id at https://blackfire.io/my/settings/credentials
@@ -36,7 +36,7 @@ in {
 
             server-token = lib.mkOption {
               type = lib.types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Sets the server token used to authenticate with Blackfire
 
                 You can find your personal server-token at https://blackfire.io/my/settings/credentials
diff --git a/nixos/modules/services/development/bloop.nix b/nixos/modules/services/development/bloop.nix
index c1180a8bbdd4..27da76a74432 100644
--- a/nixos/modules/services/development/bloop.nix
+++ b/nixos/modules/services/development/bloop.nix
@@ -17,7 +17,7 @@ in {
         "-J-XX:MaxInlineLevel=20"
         "-J-XX:+UseParallelGC"
       ];
-      description = ''
+      description = lib.mdDoc ''
         Specifies additional command line argument to pass to bloop
         java process.
       '';
@@ -26,7 +26,7 @@ in {
     install = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to install a user service for the Bloop server.
 
         The service must be manually started for each user with
diff --git a/nixos/modules/services/development/distccd.nix b/nixos/modules/services/development/distccd.nix
index 9f6d5c813c45..a3c909eb1959 100644
--- a/nixos/modules/services/development/distccd.nix
+++ b/nixos/modules/services/development/distccd.nix
@@ -8,13 +8,13 @@ in
 {
   options = {
     services.distccd = {
-      enable = mkEnableOption "distccd";
+      enable = mkEnableOption (lib.mdDoc "distccd");
 
       allowedClients = mkOption {
         type = types.listOf types.str;
         default = [ "127.0.0.1" ];
         example = [ "127.0.0.1" "192.168.0.0/24" "10.0.0.0/24" ];
-        description = ''
+        description = lib.mdDoc ''
           Client IPs which are allowed to connect to distccd in CIDR notation.
 
           Anyone who can connect to the distccd server can run arbitrary
@@ -26,7 +26,7 @@ in
       jobTimeout = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Maximum duration, in seconds, of a single compilation request.
         '';
       };
@@ -34,7 +34,7 @@ in
       logLevel = mkOption {
         type = types.nullOr (types.enum [ "critical" "error" "warning" "notice" "info" "debug" ]);
         default = "warning";
-        description = ''
+        description = lib.mdDoc ''
           Set the minimum severity of error that will be included in the log
           file. Useful if you only want to see error messages rather than an
           entry for each connection.
@@ -44,7 +44,7 @@ in
       maxJobs = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of tasks distccd should execute at any time.
         '';
       };
@@ -53,7 +53,7 @@ in
       nice = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Niceness of the compilation tasks.
         '';
       };
@@ -61,7 +61,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Opens the specified TCP port for distcc.
         '';
       };
@@ -70,7 +70,7 @@ in
         type = types.package;
         default = pkgs.distcc;
         defaultText = literalExpression "pkgs.distcc";
-        description = ''
+        description = lib.mdDoc ''
           The distcc package to use.
         '';
       };
@@ -78,17 +78,17 @@ in
       port = mkOption {
         type = types.port;
         default = 3632;
-        description = ''
+        description = lib.mdDoc ''
           The TCP port which distccd will listen on.
         '';
       };
 
       stats = {
-        enable = mkEnableOption "statistics reporting via HTTP server";
+        enable = mkEnableOption (lib.mdDoc "statistics reporting via HTTP server");
         port = mkOption {
           type = types.port;
           default = 3633;
-          description = ''
+          description = lib.mdDoc ''
             The TCP port which the distccd statistics HTTP server will listen
             on.
           '';
@@ -98,7 +98,7 @@ in
       zeroconf = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to register via mDNS/DNS-SD
         '';
       };
diff --git a/nixos/modules/services/development/hoogle.nix b/nixos/modules/services/development/hoogle.nix
index 7c2a1c8e1624..88dd01fd8aab 100644
--- a/nixos/modules/services/development/hoogle.nix
+++ b/nixos/modules/services/development/hoogle.nix
@@ -14,12 +14,12 @@ let
 in {
 
   options.services.hoogle = {
-    enable = mkEnableOption "Haskell documentation server";
+    enable = mkEnableOption (lib.mdDoc "Haskell documentation server");
 
     port = mkOption {
       type = types.port;
       default = 8080;
-      description = ''
+      description = lib.mdDoc ''
         Port number Hoogle will be listening to.
       '';
     };
@@ -29,17 +29,17 @@ 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.
       '';
     };
 
     haskellPackages = mkOption {
-      description = "Which haskell package set to use.";
+      description = lib.mdDoc "Which haskell package set to use.";
       type = types.attrs;
       default = pkgs.haskellPackages;
       defaultText = literalExpression "pkgs.haskellPackages";
@@ -47,13 +47,13 @@ in {
 
     home = mkOption {
       type = types.str;
-      description = "Url for hoogle logo";
+      description = lib.mdDoc "Url for hoogle logo";
       default = "https://hoogle.haskell.org";
     };
 
     host = mkOption {
       type = types.str;
-      description = "Set the host to bind on.";
+      description = lib.mdDoc "Set the host to bind on.";
       default = "127.0.0.1";
     };
   };
diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix
index 4eacc4782a9a..9f7910844468 100644
--- a/nixos/modules/services/development/jupyter/default.nix
+++ b/nixos/modules/services/development/jupyter/default.nix
@@ -24,12 +24,12 @@ 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;
       default = "localhost";
-      description = ''
+      description = lib.mdDoc ''
         IP address Jupyter will be listening on.
       '';
     };
@@ -41,7 +41,7 @@ in {
       # saving a rebuild.
       default = pkgs.python3.pkgs.notebook;
       defaultText = literalExpression "pkgs.python3.pkgs.notebook";
-      description = ''
+      description = lib.mdDoc ''
         Jupyter package to use.
       '';
     };
@@ -50,16 +50,16 @@ in {
       type = types.str;
       default = "jupyter-notebook";
       example = "jupyter-lab";
-      description = ''
+      description = lib.mdDoc ''
         Which command the service runs. Note that not all jupyter packages
         have all commands, e.g. jupyter-lab isn't present in the default package.
        '';
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8888;
-      description = ''
+      description = lib.mdDoc ''
         Port number Jupyter will be listening on.
       '';
     };
@@ -67,7 +67,7 @@ in {
     notebookDir = mkOption {
       type = types.str;
       default = "~/";
-      description = ''
+      description = lib.mdDoc ''
         Root directory for notebooks.
       '';
     };
@@ -75,7 +75,7 @@ in {
     user = mkOption {
       type = types.str;
       default = "jupyter";
-      description = ''
+      description = lib.mdDoc ''
         Name of the user used to run the jupyter service.
         For security reason, jupyter should really not be run as root.
         If not set (jupyter), the service will create a jupyter user with appropriate settings.
@@ -86,7 +86,7 @@ in {
     group = mkOption {
       type = types.str;
       default = "jupyter";
-      description = ''
+      description = lib.mdDoc ''
         Name of the group used to run the jupyter service.
         Use this if you want to create a group of users that are able to view the notebook directory's content.
       '';
@@ -95,7 +95,7 @@ in {
 
     password = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Password to use with notebook.
         Can be generated using:
           In [1]: from notebook.auth import passwd
@@ -112,14 +112,14 @@ in {
     notebookConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Raw jupyter config.
       '';
     };
 
     kernels = mkOption {
       type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix {
-        inherit lib;
+        inherit lib pkgs;
       })));
 
       default = null;
@@ -143,16 +143,20 @@ in {
             language = "python";
             logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png";
             logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png";
+            extraPaths = {
+              "cool.txt" = pkgs.writeText "cool" "cool content";
+            };
           };
         }
       '';
-      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/nixos/modules/services/development/jupyter/kernel-options.nix b/nixos/modules/services/development/jupyter/kernel-options.nix
index 348a8b44b382..6e406152de47 100644
--- a/nixos/modules/services/development/jupyter/kernel-options.nix
+++ b/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 {
@@ -13,7 +15,7 @@ with lib;
         "Python 3"
         "Python 3 for Data Science"
       '';
-      description = ''
+      description = lib.mdDoc ''
         Name that will be shown to the user.
       '';
     };
@@ -27,7 +29,7 @@ with lib;
         "-f"
         "{connection_file}"
       ];
-      description = ''
+      description = lib.mdDoc ''
         Command and arguments to start the kernel.
       '';
     };
@@ -35,16 +37,25 @@ with lib;
     language = mkOption {
       type = types.str;
       example = "python";
-      description = ''
+      description = lib.mdDoc ''
         Language of the environment. Typically the name of the binary.
       '';
     };
 
+    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;
       example = literalExpression ''"''${env.sitePackages}/ipykernel/resources/logo-32x32.png"'';
-      description = ''
+      description = lib.mdDoc ''
         Path to 32x32 logo png.
       '';
     };
@@ -52,9 +63,18 @@ with lib;
       type = types.nullOr types.path;
       default = null;
       example = literalExpression ''"''${env.sitePackages}/ipykernel/resources/logo-64x64.png"'';
-      description = ''
+      description = lib.mdDoc ''
         Path to 64x64 logo png.
       '';
     };
+
+    extraPaths = mkOption {
+      type = types.attrsOf types.path;
+      default = { };
+      example = literalExpression ''"{ examples = ''${env.sitePack}/IRkernel/kernelspec/kernel.js"; }'';
+      description = lib.mdDoc ''
+        Extra paths to link in kernel directory
+      '';
+    };
   };
 }
diff --git a/nixos/modules/services/development/jupyterhub/default.nix b/nixos/modules/services/development/jupyterhub/default.nix
index fa6b3be960ab..cebc35a50476 100644
--- a/nixos/modules/services/development/jupyterhub/default.nix
+++ b/nixos/modules/services/development/jupyterhub/default.nix
@@ -30,12 +30,12 @@ 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;
       default = "jupyterhub.auth.PAMAuthenticator";
-      description = ''
+      description = lib.mdDoc ''
         Jupyterhub authentication to use
 
         There are many authenticators available including: oauth, pam,
@@ -46,7 +46,7 @@ in {
     spawner = mkOption {
       type = types.str;
       default = "systemdspawner.SystemdSpawner";
-      description = ''
+      description = lib.mdDoc ''
         Jupyterhub spawner to use
 
         There are many spawners available including: local process,
@@ -57,7 +57,7 @@ in {
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra contents appended to the jupyterhub configuration
 
         Jupyterhub configuration is a normal python file using
@@ -84,7 +84,7 @@ in {
           jupyterhub-systemdspawner
         ])
       '';
-      description = ''
+      description = lib.mdDoc ''
         Python environment to run jupyterhub
 
         Customizing will affect the packages available in the hub and
@@ -106,7 +106,7 @@ in {
           jupyterlab
         ])
       '';
-      description = ''
+      description = lib.mdDoc ''
         Python environment to run jupyterlab
 
         Customizing will affect the packages available in the
@@ -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;
@@ -146,7 +146,7 @@ in {
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Declarative kernel config
 
         Kernels can be declared in any language that supports and has
@@ -159,7 +159,7 @@ in {
     port = mkOption {
       type = types.port;
       default = 8000;
-      description = ''
+      description = lib.mdDoc ''
         Port number Jupyterhub will be listening on
       '';
     };
@@ -167,7 +167,7 @@ in {
     host = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = ''
+      description = lib.mdDoc ''
         Bind IP JupyterHub will be listening on
       '';
     };
@@ -175,7 +175,7 @@ in {
     stateDirectory = mkOption {
       type = types.str;
       default = "jupyterhub";
-      description = ''
+      description = lib.mdDoc ''
         Directory for jupyterhub state (token + database)
       '';
     };
diff --git a/nixos/modules/services/development/lorri.nix b/nixos/modules/services/development/lorri.nix
index bda63518bfd9..8c64e3d9a560 100644
--- a/nixos/modules/services/development/lorri.nix
+++ b/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.
@@ -18,7 +18,7 @@ in {
       package = lib.mkOption {
         default = pkgs.lorri;
         type = lib.types.package;
-        description = ''
+        description = lib.mdDoc ''
           The lorri package to use.
         '';
         defaultText = lib.literalExpression "pkgs.lorri";
diff --git a/nixos/modules/services/development/rstudio-server/default.nix b/nixos/modules/services/development/rstudio-server/default.nix
index cd903c7e55bf..bf4c7727bf74 100644
--- a/nixos/modules/services/development/rstudio-server/default.nix
+++ b/nixos/modules/services/development/rstudio-server/default.nix
@@ -21,12 +21,12 @@ 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;
       default = "/var/lib/rstudio-server";
-      description = ''
+      description = lib.mdDoc ''
         Default working directory for server (server-working-dir in rserver.conf).
       '';
     };
@@ -34,7 +34,7 @@ in
     listenAddr = mkOption {
       type = types.str;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         Address to listen on (www-address in rserver.conf).
       '';
     };
@@ -44,7 +44,7 @@ in
       default = pkgs.rstudio-server;
       defaultText = literalExpression "pkgs.rstudio-server";
       example = literalExpression "pkgs.rstudioServerWrapper.override { packages = [ pkgs.rPackages.ggplot2 ]; }";
-      description = ''
+      description = lib.mdDoc ''
         Rstudio server package to use. Can be set to rstudioServerWrapper to provide packages.
       '';
     };
@@ -52,7 +52,7 @@ in
     rserverExtraConfig = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra contents for rserver.conf.
       '';
     };
@@ -60,7 +60,7 @@ in
     rsessionExtraConfig = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra contents for resssion.conf.
       '';
     };
diff --git a/nixos/modules/services/development/zammad.nix b/nixos/modules/services/development/zammad.nix
index d457a6071873..7de11b08b7e7 100644
--- a/nixos/modules/services/development/zammad.nix
+++ b/nixos/modules/services/development/zammad.nix
@@ -28,19 +28,19 @@ 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;
         default = pkgs.zammad;
         defaultText = literalExpression "pkgs.zammad";
-        description = "Zammad package to use.";
+        description = lib.mdDoc "Zammad package to use.";
       };
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/zammad";
-        description = ''
+        description = lib.mdDoc ''
           Path to a folder that will contain Zammad working directory.
         '';
       };
@@ -49,25 +49,25 @@ in
         type = types.str;
         default = "127.0.0.1";
         example = "192.168.23.42";
-        description = "Host address.";
+        description = lib.mdDoc "Host address.";
       };
 
       openPorts = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to open firewall ports for Zammad";
+        description = lib.mdDoc "Whether to open firewall ports for Zammad";
       };
 
       port = mkOption {
         type = types.port;
         default = 3000;
-        description = "Web service port.";
+        description = lib.mdDoc "Web service port.";
       };
 
       websocketPort = mkOption {
         type = types.port;
         default = 6042;
-        description = "Websocket service port.";
+        description = lib.mdDoc "Websocket service port.";
       };
 
       database = {
@@ -75,7 +75,7 @@ in
           type = types.enum [ "PostgreSQL" "MySQL" ];
           default = "PostgreSQL";
           example = "MySQL";
-          description = "Database engine to use.";
+          description = lib.mdDoc "Database engine to use.";
         };
 
         host = mkOption {
@@ -90,7 +90,7 @@ in
               MySQL = "localhost";
             }.''${config.services.zammad.database.type};
           '';
-          description = ''
+          description = lib.mdDoc ''
             Database host address.
           '';
         };
@@ -98,13 +98,13 @@ in
         port = mkOption {
           type = types.nullOr types.port;
           default = null;
-          description = "Database port. Use <literal>null</literal> for default port.";
+          description = lib.mdDoc "Database port. Use `null` for default port.";
         };
 
         name = mkOption {
           type = types.str;
           default = "zammad";
-          description = ''
+          description = lib.mdDoc ''
             Database name.
           '';
         };
@@ -112,22 +112,22 @@ in
         user = mkOption {
           type = types.nullOr types.str;
           default = "zammad";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/zammad-dbpassword";
-          description = ''
-            A file containing the password for <option>services.zammad.database.user</option>.
+          description = lib.mdDoc ''
+            A file containing the password for {option}`services.zammad.database.user`.
           '';
         };
 
         createLocally = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether to create a local database automatically.";
+          description = lib.mdDoc "Whether to create a local database automatically.";
         };
 
         settings = mkOption {
@@ -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.
diff --git a/nixos/modules/services/display-managers/greetd.nix b/nixos/modules/services/display-managers/greetd.nix
index 895961707d36..96eddc1ce7cd 100644
--- a/nixos/modules/services/display-managers/greetd.nix
+++ b/nixos/modules/services/display-managers/greetd.nix
@@ -8,13 +8,13 @@ let
 in
 {
   options.services.greetd = {
-    enable = mkEnableOption "greetd";
+    enable = mkEnableOption (lib.mdDoc "greetd");
 
     package = mkOption {
       type = types.package;
       default = pkgs.greetd.greetd;
       defaultText = literalExpression "pkgs.greetd.greetd";
-      description = "The greetd package that should be used.";
+      description = lib.mdDoc "The greetd package that should be used.";
     };
 
     settings = mkOption {
@@ -26,8 +26,8 @@ in
           };
         }
       '';
-      description = ''
-        greetd configuration (<link xlink:href="https://man.sr.ht/~kennylevinsen/greetd/">documentation</link>)
+      description = lib.mdDoc ''
+        greetd configuration ([documentation](https://man.sr.ht/~kennylevinsen/greetd/))
         as a Nix attribute set.
       '';
     };
@@ -35,7 +35,7 @@ in
     vt = mkOption  {
       type = types.int;
       default = 1;
-      description = ''
+      description = lib.mdDoc ''
         The virtual console (tty) that greetd should use. This option also disables getty on that tty.
       '';
     };
@@ -44,8 +44,8 @@ in
       type = types.bool;
       default = !(cfg.settings ? initial_session);
       defaultText = literalExpression "!(config.services.greetd.settings ? initial_session)";
-      description = ''
-        Wether to restart greetd when it terminates (e.g. on failure).
+      description = lib.mdDoc ''
+        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.
       '';
@@ -54,7 +54,7 @@ in
   config = mkIf cfg.enable {
 
     services.greetd.settings.terminal.vt = mkDefault cfg.vt;
-    services.greetd.settings.default_session = mkDefault "greeter";
+    services.greetd.settings.default_session.user = mkDefault "greeter";
 
     security.pam.services.greetd = {
       allowNullPassword = true;
diff --git a/nixos/modules/services/editors/emacs.nix b/nixos/modules/services/editors/emacs.nix
index e2bbd27f6e56..5ae28cd9bbb3 100644
--- a/nixos/modules/services/editors/emacs.nix
+++ b/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`.
       '';
     };
 
@@ -67,7 +67,7 @@ in
       type = types.package;
       default = pkgs.emacs;
       defaultText = literalExpression "pkgs.emacs";
-      description = ''
+      description = lib.mdDoc ''
         emacs derivation to use.
       '';
     };
@@ -75,7 +75,7 @@ in
     defaultEditor = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         When enabled, configures emacsclient to be the default editor
         using the EDITOR environment variable.
       '';
diff --git a/nixos/modules/services/editors/haste.nix b/nixos/modules/services/editors/haste.nix
index 35fe26766ef7..a46415d43634 100644
--- a/nixos/modules/services/editors/haste.nix
+++ b/nixos/modules/services/editors/haste.nix
@@ -10,13 +10,13 @@ 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 = ''
+      description = lib.mdDoc ''
         Configuration for haste-server.
-        For documentation see <link xlink:href="https://github.com/toptal/haste-server#settings">project readme</link>
+        For documentation see [project readme](https://github.com/toptal/haste-server#settings)
       '';
       type = format.type;
     };
diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix
index 16fe52a232bd..de0989994019 100644
--- a/nixos/modules/services/editors/infinoted.nix
+++ b/nixos/modules/services/editors/infinoted.nix
@@ -6,13 +6,13 @@ let
   cfg = config.services.infinoted;
 in {
   options.services.infinoted = {
-    enable = mkEnableOption "infinoted";
+    enable = mkEnableOption (lib.mdDoc "infinoted");
 
     package = mkOption {
       type = types.package;
       default = pkgs.libinfinity;
       defaultText = literalExpression "pkgs.libinfinity";
-      description = ''
+      description = lib.mdDoc ''
         Package providing infinoted
       '';
     };
@@ -20,7 +20,7 @@ in {
     keyFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Private key to use for TLS
       '';
     };
@@ -28,7 +28,7 @@ in {
     certificateFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Server certificate to use for TLS
       '';
     };
@@ -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.
       '';
@@ -45,7 +45,7 @@ in {
     securityPolicy = mkOption {
       type = types.enum ["no-tls" "allow-tls" "require-tls"];
       default = "require-tls";
-      description = ''
+      description = lib.mdDoc ''
         How strictly to enforce clients connection with TLS.
       '';
     };
@@ -53,7 +53,7 @@ in {
     port = mkOption {
       type = types.port;
       default = 6523;
-      description = ''
+      description = lib.mdDoc ''
         Port to listen on
       '';
     };
@@ -61,7 +61,7 @@ in {
     rootDirectory = mkOption {
       type = types.path;
       default = "/var/lib/infinoted/documents/";
-      description = ''
+      description = lib.mdDoc ''
         Root of the directory structure to serve
       '';
     };
@@ -69,7 +69,7 @@ in {
     plugins = mkOption {
       type = types.listOf types.str;
       default = [ "note-text" "note-chat" "logging" "autosave" ];
-      description = ''
+      description = lib.mdDoc ''
         Plugins to enable
       '';
     };
@@ -77,7 +77,7 @@ in {
     passwordFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         File to read server-wide password from
       '';
     };
@@ -88,7 +88,7 @@ in {
         [autosave]
         interval=10
       '';
-      description = ''
+      description = lib.mdDoc ''
         Additional configuration to append to infinoted.conf
       '';
     };
@@ -96,7 +96,7 @@ in {
     user = mkOption {
       type = types.str;
       default = "infinoted";
-      description = ''
+      description = lib.mdDoc ''
         What to call the dedicated user under which infinoted is run
       '';
     };
@@ -104,7 +104,7 @@ in {
     group = mkOption {
       type = types.str;
       default = "infinoted";
-      description = ''
+      description = lib.mdDoc ''
         What to call the primary group of the dedicated user under which infinoted is run
       '';
     };
diff --git a/nixos/modules/services/finance/odoo.nix b/nixos/modules/services/finance/odoo.nix
index 422ee9510074..fee9af574b5d 100644
--- a/nixos/modules/services/finance/odoo.nix
+++ b/nixos/modules/services/finance/odoo.nix
@@ -9,33 +9,33 @@ in
 {
   options = {
     services.odoo = {
-      enable = mkEnableOption "odoo";
+      enable = mkEnableOption (lib.mdDoc "odoo");
 
       package = mkOption {
         type = types.package;
         default = pkgs.odoo;
         defaultText = literalExpression "pkgs.odoo";
-        description = "Odoo package to use.";
+        description = lib.mdDoc "Odoo package to use.";
       };
 
       addons = mkOption {
         type = with types; listOf package;
         default = [];
         example = literalExpression "[ pkgs.odoo_enterprise ]";
-        description = "Odoo addons.";
+        description = lib.mdDoc "Odoo addons.";
       };
 
       settings = mkOption {
         type = format.type;
         default = {};
-        description = ''
-          Odoo configuration settings. For more details see <link xlink:href="https://www.odoo.com/documentation/15.0/administration/install/deploy.html"/>
+        description = lib.mdDoc ''
+          Odoo configuration settings. For more details see <https://www.odoo.com/documentation/15.0/administration/install/deploy.html>
         '';
       };
 
       domain = mkOption {
         type = with types; nullOr str;
-        description = "Domain to host Odoo with nginx";
+        description = lib.mdDoc "Domain to host Odoo with nginx";
         default = null;
       };
     };
diff --git a/nixos/modules/services/games/asf.nix b/nixos/modules/services/games/asf.nix
index ea2bfd40fffa..7585d56b2d78 100644
--- a/nixos/modules/services/games/asf.nix
+++ b/nixos/modules/services/games/asf.nix
@@ -13,6 +13,8 @@ let
     # is in theory not needed as this is already the default for default builds
     UpdateChannel = 0;
     Headless = true;
+  } // lib.optionalAttrs (cfg.ipcPasswordFile != null) {
+    IPCPassword = "#ipcPassword#";
   });
 
   ipc-config = format.generate "IPC.config" cfg.ipcSettings;
@@ -30,10 +32,10 @@ in
   options.services.archisteamfarm = {
     enable = mkOption {
       type = types.bool;
-      description = ''
+      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;
     };
@@ -41,50 +43,51 @@ 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 =
-              "Web-UI package to use. Contents must be in lib/dist.";
+              lib.mdDoc "Web-UI package to use. Contents must be in lib/dist.";
           };
         };
       };
       default = {
         enable = true;
-        package = pkgs.ArchiSteamFarm.ui;
       };
       example = {
         enable = false;
       };
-      description = "The Web-UI hosted on 127.0.0.1:1242.";
+      description = lib.mdDoc "The Web-UI hosted on 127.0.0.1:1242.";
     };
 
     package = mkOption {
       type = types.package;
       default = pkgs.ArchiSteamFarm;
+      defaultText = lib.literalExpression "pkgs.ArchiSteamFarm";
       description =
-        "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.";
+        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.";
     };
 
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/asf";
-      description = ''
+      description = lib.mdDoc ''
         The ASF home directory used to store all data.
         If left as the default value this directory will automatically be created before the ASF server starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions.'';
     };
 
     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>.
-        Do note that `AutoRestart`  and `UpdateChannel` is always to `false`
-respectively `0` because NixOS takes care of updating everything.
+      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;
@@ -92,11 +95,17 @@ respectively `0` because NixOS takes care of updating everything.
       default = { };
     };
 
+    ipcPasswordFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc "Path to a file containing the password. The file must be readable by the `asf` user/group.";
+    };
+
     ipcSettings = mkOption {
       type = format.type;
-      description = ''
+      description = lib.mdDoc ''
         Settings to write to IPC.config.
-        All options can be found <link xlink:href="https://github.com/JustArchiNET/ArchiSteamFarm/wiki/IPC#custom-configuration">here</link>.
+        All options can be found [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/IPC#custom-configuration).
       '';
       example = {
         Kestrel = {
@@ -115,29 +124,28 @@ respectively `0` because NixOS takes care of updating everything.
         options = {
           username = mkOption {
             type = types.str;
-            description =
-              "Name of the user to log in. Default is attribute name.";
+            description = lib.mdDoc "Name of the user to log in. Default is attribute name.";
             default = "";
           };
           passwordFile = mkOption {
             type = types.path;
-            description =
-              "Path to a file containig the password. The file must be readable by the <literal>asf</literal> 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;
             default = true;
-            description = "Whether to enable the bot on startup.";
+            description = lib.mdDoc "Whether to enable the bot on startup.";
           };
           settings = mkOption {
             type = types.attrs;
-            description =
-              "Additional settings that are documented <link xlink:href=\"https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config\">here</link>.";
+            description = lib.mdDoc ''
+              Additional settings that are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config).
+            '';
             default = { };
           };
         };
       });
-      description = ''
+      description = lib.mdDoc ''
         Bots name and configuration.
       '';
       example = {
@@ -170,14 +178,17 @@ respectively `0` because NixOS takes care of updating everything.
         wantedBy = [ "multi-user.target" ];
 
         serviceConfig = mkMerge [
-          (mkIf (cfg.dataDir == "/var/lib/asf") { StateDirectory = "asf"; })
+          (mkIf (cfg.dataDir == "/var/lib/asf") {
+            StateDirectory = "asf";
+            StateDirectoryMode = "700";
+          })
           {
             User = "asf";
             Group = "asf";
             WorkingDirectory = cfg.dataDir;
             Type = "simple";
-            ExecStart =
-              "${cfg.package}/bin/ArchiSteamFarm --path ${cfg.dataDir} --process-required --no-restart --service --no-config-migrate";
+            ExecStart = "${cfg.package}/bin/ArchiSteamFarm --path ${cfg.dataDir} --process-required --no-restart --service --no-config-migrate";
+            Restart = "always";
 
             # mostly copied from the default systemd service
             PrivateTmp = true;
@@ -202,35 +213,47 @@ respectively `0` because NixOS takes care of updating everything.
           }
         ];
 
-        preStart = ''
-          mkdir -p config
-          rm -f www
-          rm -f config/{*.json,*.config}
-
-          ln -s ${asf-config} config/ASF.json
-
-          ${strings.optionalString (cfg.ipcSettings != {}) ''
-            ln -s ${ipc-config} config/IPC.config
-          ''}
-
-          ln -s ${pkgs.runCommandLocal "ASF-bots" {} ''
-            mkdir -p $out/lib/asf/bots
-            for i in ${strings.concatStringsSep " " (lists.map (x: "${getName x},${x}") (attrsets.mapAttrsToList mkBot cfg.bots))}; do IFS=",";
-              set -- $i
-              ln -s $2 $out/lib/asf/bots/$1
-            done
-          ''}/lib/asf/bots/* config/
-
-          ${strings.optionalString cfg.web-ui.enable ''
-            ln -s ${cfg.web-ui.package}/lib/dist www
-          ''}
-        '';
+        preStart =
+          let
+            createBotsScript = pkgs.runCommandLocal "ASF-bots" { } ''
+              mkdir -p $out
+              # clean potential removed bots
+              rm -rf $out/*.json
+              for i in ${strings.concatStringsSep " " (lists.map (x: "${getName x},${x}") (attrsets.mapAttrsToList mkBot cfg.bots))}; do IFS=",";
+                set -- $i
+                ln -fs $2 $out/$1
+              done
+            '';
+            replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
+          in
+          ''
+            mkdir -p config
+
+            cp --no-preserve=mode ${asf-config} config/ASF.json
+
+            ${optionalString (cfg.ipcPasswordFile != null) ''
+              ${replaceSecretBin} '#ipcPassword#' '${cfg.ipcPasswordFile}' config/ASF.json
+            ''}
+
+            ${optionalString (cfg.ipcSettings != {}) ''
+              ln -fs ${ipc-config} config/IPC.config
+            ''}
+
+            ${optionalString (cfg.ipcSettings != {}) ''
+              ln -fs ${createBotsScript}/* config/
+            ''}
+
+            rm -f www
+            ${optionalString cfg.web-ui.enable ''
+              ln -s ${cfg.web-ui.package}/lib/dist www
+            ''}
+          '';
       };
     };
   };
 
   meta = {
     buildDocsInSandbox = false;
-    maintainers = with maintainers; [ lom ];
+    maintainers = with maintainers; [ lom SuperSandro2000 ];
   };
 }
diff --git a/nixos/modules/services/games/crossfire-server.nix b/nixos/modules/services/games/crossfire-server.nix
index a33025e0c3e1..0849667e61c9 100644
--- a/nixos/modules/services/games/crossfire-server.nix
+++ b/nixos/modules/services/games/crossfire-server.nix
@@ -10,7 +10,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If enabled, the Crossfire game server will be started at boot.
       '';
     };
@@ -19,7 +19,7 @@ in {
       type = types.package;
       default = pkgs.crossfire-server;
       defaultText = literalExpression "pkgs.crossfire-server";
-      description = ''
+      description = lib.mdDoc ''
         The package to use for the Crossfire server (and map/arch data, if you
         don't change dataDir).
       '';
@@ -29,7 +29,7 @@ in {
       type = types.str;
       default = "${cfg.package}/share/crossfire";
       defaultText = literalExpression ''"''${config.services.crossfire.package}/share/crossfire"'';
-      description = ''
+      description = lib.mdDoc ''
         Where to load readonly data from -- maps, archetypes, treasure tables,
         and the like. If you plan to edit the data on the live server (rather
         than overlaying the crossfire-maps and crossfire-arch packages and
@@ -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
@@ -54,14 +54,14 @@ in {
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to open ports in the firewall for the server.
       '';
     };
 
     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
@@ -76,7 +76,7 @@ in {
         {
           dm_file = '''
             admin:secret_password:localhost
-            jane:xyzzy:*
+            alice:xyzzy:*
           ''';
           ban_file = '''
             # Bob is a jerk
@@ -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/nixos/modules/services/games/deliantra-server.nix b/nixos/modules/services/games/deliantra-server.nix
index b7011f4c3542..f39044eda7c7 100644
--- a/nixos/modules/services/games/deliantra-server.nix
+++ b/nixos/modules/services/games/deliantra-server.nix
@@ -10,7 +10,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If enabled, the Deliantra game server will be started at boot.
       '';
     };
@@ -19,7 +19,7 @@ in {
       type = types.package;
       default = pkgs.deliantra-server;
       defaultText = literalExpression "pkgs.deliantra-server";
-      description = ''
+      description = lib.mdDoc ''
         The package to use for the Deliantra server (and map/arch data, if you
         don't change dataDir).
       '';
@@ -29,7 +29,7 @@ in {
       type = types.str;
       default = "${pkgs.deliantra-data}";
       defaultText = literalExpression ''"''${pkgs.deliantra-data}"'';
-      description = ''
+      description = lib.mdDoc ''
         Where to store readonly data (maps, archetypes, sprites, etc).
         Note that if you plan to use the live map editor (rather than editing
         the maps offline and then nixos-rebuilding), THIS MUST BE WRITEABLE --
@@ -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
@@ -54,14 +54,14 @@ in {
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to open ports in the firewall for the server.
       '';
     };
 
     configFiles = mkOption {
       type = types.attrsOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         Contents of the server configuration files. These will be appended to
         the example configurations the server comes with and overwrite any
         default settings defined therein.
@@ -73,7 +73,7 @@ in {
         {
           dm_file = '''
             admin:secret_password:localhost
-            jane:xyzzy:*
+            alice:xyzzy:*
           ''';
           motd = "Welcome to Deliantra!";
           settings = '''
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index ff73d7a46ed3..9b15cac149d5 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -39,16 +39,16 @@ 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 = ''
+        description = lib.mdDoc ''
           The port to which the service should bind.
         '';
       };
@@ -56,7 +56,7 @@ in
       bind = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           The address to which the service should bind.
         '';
       };
@@ -65,7 +65,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "username" ];
-        description = ''
+        description = lib.mdDoc ''
           List of player names which will be admin.
         '';
       };
@@ -73,20 +73,32 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to automatically open the specified UDP port in the firewall.
         '';
       };
       saveName = mkOption {
         type = types.str;
         default = "default";
-        description = ''
+        description = lib.mdDoc ''
           The name of the savegame that will be used by the server.
 
           When not present in /var/lib/''${config.services.factorio.stateDirName}/saves,
           a new map with default settings will be generated before starting the service.
         '';
       };
+      loadLatestSave = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Load the latest savegame on startup. This overrides saveName, in that the latest
+          save will always be used even if a saved game of the given name exists. It still
+          controls the 'canonical' name of the savegame.
+
+          Set this to true to have the server automatically reload a recent autosave after
+          a crash or desync.
+        '';
+      };
       # TODO Add more individual settings as nixos-options?
       # TODO XXX The server tries to copy a newly created config file over the old one
       #   on shutdown, but fails, because it's in the nix store. When is this needed?
@@ -95,7 +107,7 @@ in
         type = types.path;
         default = configFile;
         defaultText = literalExpression "configFile";
-        description = ''
+        description = lib.mdDoc ''
           The server's configuration file.
 
           The default file generated by this module contains lines essential to
@@ -106,7 +118,7 @@ in
       stateDirName = mkOption {
         type = types.str;
         default = "factorio";
-        description = ''
+        description = lib.mdDoc ''
           Name of the directory under /var/lib holding the server's data.
 
           The configuration and map will be stored here.
@@ -115,7 +127,7 @@ in
       mods = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Mods the server should install and activate.
 
           The derivations in this list must "build" the mod by simply copying
@@ -124,17 +136,26 @@ 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";
-        description = ''
+        description = lib.mdDoc ''
           Name of the game as it will appear in the game listing.
         '';
       };
       description = mkOption {
         type = types.nullOr types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Description of the game that will appear in the listing.
         '';
       };
@@ -142,28 +163,28 @@ in
         type = types.attrs;
         default = {};
         example = { admins = [ "username" ];};
-        description = ''
+        description = lib.mdDoc ''
           Extra game configuration that will go into server-settings.json
         '';
       };
       public = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Game will be published on the official Factorio matching server.
         '';
       };
       lan = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Game will be broadcast on LAN.
         '';
       };
       username = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Your factorio.com login credentials. Required for games with visibility public.
         '';
       };
@@ -172,35 +193,35 @@ in
         default = pkgs.factorio-headless;
         defaultText = literalExpression "pkgs.factorio-headless";
         example = literalExpression "pkgs.factorio-headless-experimental";
-        description = ''
+        description = lib.mdDoc ''
           Factorio version to use. This defaults to the stable channel.
         '';
       };
       password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Your factorio.com login credentials. Required for games with visibility public.
         '';
       };
       token = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Authentication token. May be used instead of 'password' above.
         '';
       };
       game-password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Game password.
         '';
       };
       requireUserVerification = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           When set to true, the server will only allow clients that have a valid factorio.com account.
         '';
       };
@@ -208,14 +229,14 @@ in
         type = types.nullOr types.int;
         default = null;
         example = 10;
-        description = ''
+        description = lib.mdDoc ''
           Autosave interval in minutes.
         '';
       };
       nonBlockingSaving = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Highly experimental feature, enable only at your own risk of losing your saves.
           On UNIX systems, server will fork itself to create an autosave.
           Autosaving on connected Windows clients will be disabled regardless of autosave_only_on_server option.
@@ -250,8 +271,9 @@ in
           "--config=${cfg.configFile}"
           "--port=${toString cfg.port}"
           "--bind=${cfg.bind}"
-          "--start-server=${mkSavePath cfg.saveName}"
+          (optionalString (!cfg.loadLatestSave) "--start-server=${mkSavePath cfg.saveName}")
           "--server-settings=${serverSettingsFile}"
+          (optionalString cfg.loadLatestSave "--start-server-load-latest")
           (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
           (optionalString (cfg.admins != []) "--server-adminlist=${serverAdminsFile}")
         ];
diff --git a/nixos/modules/services/games/freeciv.nix b/nixos/modules/services/games/freeciv.nix
index 4923891a6179..8b340bb161a5 100644
--- a/nixos/modules/services/games/freeciv.nix
+++ b/nixos/modules/services/games/freeciv.nix
@@ -25,9 +25,9 @@ in
 {
   options = {
     services.freeciv = {
-      enable = mkEnableOption ''freeciv'';
+      enable = mkEnableOption (lib.mdDoc ''freeciv'');
       settings = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Parameters of freeciv-server.
         '';
         default = {};
@@ -36,9 +36,9 @@ in
           options.Announce = mkOption {
             type = types.enum ["IPv4" "IPv6" "none"];
             default = "none";
-            description = "Announce game in LAN using given protocol.";
+            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";
@@ -47,25 +47,25 @@ in
                 backend="sqlite"
                 database="/var/lib/freeciv/auth.sqlite"
             '';
-            description = "Enable database connection with given configuration.";
+            description = lib.mdDoc "Enable database connection with given configuration.";
           };
           options.debug = mkOption {
             type = types.ints.between 0 3;
             default = 0;
-            description = "Set debug log level.";
+            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;
-            description = "Listen for clients on given port";
+            description = lib.mdDoc "Listen for clients on given port";
           };
           options.quitidle = mkOption {
             type = types.nullOr types.int;
             default = null;
-            description = "Quit if no players for given time in seconds.";
+            description = lib.mdDoc "Quit if no players for given time in seconds.";
           };
           options.read = mkOption {
             type = types.lines;
@@ -73,12 +73,12 @@ in
             default = ''
               /fcdb lua sqlite_createdb()
             '';
-            description = "Startup script.";
+            description = lib.mdDoc "Startup script.";
           };
           options.saves = mkOption {
             type = types.nullOr types.str;
             default = "/var/lib/freeciv/saves/";
-            description = ''
+            description = lib.mdDoc ''
               Save games to given directory,
               a sub-directory named after the starting date of the service
               will me inserted to preserve older saves.
@@ -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/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index 8233962c1a2c..77f92ab97db7 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -22,6 +22,15 @@ let
   '' + concatStringsSep "\n" (mapAttrsToList
     (n: v: "${n}=${cfgToString v}") cfg.serverProperties));
 
+  stopScript = pkgs.writeShellScript "minecraft-server-stop" ''
+    echo stop > ${config.systemd.sockets.minecraft-server.socketConfig.ListenFIFO}
+
+    # Wait for the PID of the minecraft server to disappear before
+    # returning, so systemd doesn't attempt to SIGKILL it.
+    while kill -0 "$1" 2> /dev/null; do
+      sleep 1s
+    done
+  '';
 
   # To be able to open the firewall, we need to read out port values in the
   # server properties, but fall back to the defaults when those don't exist.
@@ -45,21 +54,21 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, start a Minecraft Server. The server
           data will be loaded from and saved to
-          <option>services.minecraft-server.dataDir</option>.
+          {option}`services.minecraft-server.dataDir`.
         '';
       };
 
       declarative = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use a declarative Minecraft server configuration.
-          Only if set to <literal>true</literal>, the options
-          <option>services.minecraft-server.whitelist</option> and
-          <option>services.minecraft-server.serverProperties</option> will be
+          Only if set to `true`, the options
+          {option}`services.minecraft-server.whitelist` and
+          {option}`services.minecraft-server.serverProperties` will be
           applied.
         '';
       };
@@ -67,18 +76,18 @@ in {
       eula = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether you agree to
-          <link xlink:href="https://account.mojang.com/documents/minecraft_eula">
-          Mojangs EULA</link>. This option must be set to
-          <literal>true</literal> to run Minecraft server.
+          [
+          Mojangs EULA](https://account.mojang.com/documents/minecraft_eula). This option must be set to
+          `true` to run Minecraft server.
         '';
       };
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/minecraft";
-        description = ''
+        description = lib.mdDoc ''
           Directory to store Minecraft database and other state/data files.
         '';
       };
@@ -86,7 +95,7 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to open ports in the firewall for the server.
         '';
       };
@@ -99,14 +108,14 @@ in {
             };
           in types.attrsOf minecraftUUID;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Whitelisted players, only has an effect when
-          <option>services.minecraft-server.declarative</option> is
-          <literal>true</literal> and the whitelist is enabled
-          via <option>services.minecraft-server.serverProperties</option> by
-          setting <literal>white-list</literal> to <literal>true</literal>.
+          {option}`services.minecraft-server.declarative` is
+          `true` and the whitelist is enabled
+          via {option}`services.minecraft-server.serverProperties` by
+          setting `white-list` to `true`.
           This is a mapping from Minecraft usernames to UUIDs.
-          You can use <link xlink:href="https://mcuuid.net/"/> to get a
+          You can use <https://mcuuid.net/> to get a
           Minecraft UUID for a username.
         '';
         example = literalExpression ''
@@ -132,11 +141,11 @@ in {
             "rcon.password" = "hunter2";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Minecraft server properties for the server.properties file. Only has
-          an effect when <option>services.minecraft-server.declarative</option>
-          is set to <literal>true</literal>. See
-          <link xlink:href="https://minecraft.gamepedia.com/Server.properties#Java_Edition_3"/>
+          an effect when {option}`services.minecraft-server.declarative`
+          is set to `true`. See
+          <https://minecraft.gamepedia.com/Server.properties#Java_Edition_3>
           for documentation on these values.
         '';
       };
@@ -146,7 +155,7 @@ in {
         default = pkgs.minecraft-server;
         defaultText = literalExpression "pkgs.minecraft-server";
         example = literalExpression "pkgs.minecraft-server_1_12_2";
-        description = "Version of minecraft-server to run.";
+        description = lib.mdDoc "Version of minecraft-server to run.";
       };
 
       jvmOpts = mkOption {
@@ -156,7 +165,7 @@ in {
         example = "-Xms4092M -Xmx4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
           + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
           + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
-        description = "JVM options for the Minecraft server.";
+        description = lib.mdDoc "JVM options for the Minecraft server.";
       };
     };
   };
@@ -172,16 +181,35 @@ in {
     };
     users.groups.minecraft = {};
 
+    systemd.sockets.minecraft-server = {
+      bindsTo = [ "minecraft-server.service" ];
+      socketConfig = {
+        ListenFIFO = "/run/minecraft-server.stdin";
+        SocketMode = "0660";
+        SocketUser = "minecraft";
+        SocketGroup = "minecraft";
+        RemoveOnStop = true;
+        FlushPending = true;
+      };
+    };
+
     systemd.services.minecraft-server = {
       description   = "Minecraft Server Service";
       wantedBy      = [ "multi-user.target" ];
-      after         = [ "network.target" ];
+      requires      = [ "minecraft-server.socket" ];
+      after         = [ "network.target" "minecraft-server.socket" ];
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
+        ExecStop = "${stopScript} $MAINPID";
         Restart = "always";
         User = "minecraft";
         WorkingDirectory = cfg.dataDir;
+
+        StandardInput = "socket";
+        StandardOutput = "journal";
+        StandardError = "journal";
+
         # Hardening
         CapabilityBoundingSet = [ "" ];
         DeviceAllow = [ "" ];
diff --git a/nixos/modules/services/games/minetest-server.nix b/nixos/modules/services/games/minetest-server.nix
index 2111c970d4f2..e8c96881673b 100644
--- a/nixos/modules/services/games/minetest-server.nix
+++ b/nixos/modules/services/games/minetest-server.nix
@@ -19,13 +19,13 @@ in
       enable = mkOption {
         type        = types.bool;
         default     = false;
-        description = "If enabled, starts a Minetest Server.";
+        description = lib.mdDoc "If enabled, starts a Minetest Server.";
       };
 
       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:
@@ -58,18 +58,18 @@ in
       logPath = mkOption {
         type        = types.nullOr types.path;
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           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.
         '';
       };
 
       port = mkOption {
         type        = types.nullOr types.int;
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           Port number to bind to.
 
           If set to null, the default 30000 will be used.
diff --git a/nixos/modules/services/games/openarena.nix b/nixos/modules/services/games/openarena.nix
index 9c441e98b206..89e30d7c12af 100644
--- a/nixos/modules/services/games/openarena.nix
+++ b/nixos/modules/services/games/openarena.nix
@@ -8,18 +8,18 @@ in
 {
   options = {
     services.openarena = {
-      enable = mkEnableOption "OpenArena";
+      enable = mkEnableOption (lib.mdDoc "OpenArena");
 
       openPorts = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to open firewall ports for OpenArena";
+        description = lib.mdDoc "Whether to open firewall ports for OpenArena";
       };
 
       extraFlags = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Extra flags to pass to <command>oa_ded</command>";
+        description = lib.mdDoc "Extra flags to pass to {command}`oa_ded`";
         example = [
           "+set dedicated 2"
           "+set sv_hostname 'My NixOS OpenArena Server'"
diff --git a/nixos/modules/services/games/quake3-server.nix b/nixos/modules/services/games/quake3-server.nix
index 175af4a83828..2d2148237da1 100644
--- a/nixos/modules/services/games/quake3-server.nix
+++ b/nixos/modules/services/games/quake3-server.nix
@@ -37,12 +37,12 @@ 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;
         default = 27960;
-        description = ''
+        description = lib.mdDoc ''
           UDP Port the server should listen on.
         '';
       };
@@ -50,7 +50,7 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open the firewall.
         '';
       };
@@ -62,7 +62,7 @@ in {
           seta rconPassword "superSecret"      // sets RCON password for remote console
           seta sv_hostname "My Quake 3 server"      // name that appears in server list
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options. Note that options changed via RCON will not be persisted. To list all possible
           options, use "cvarlist 1" via RCON.
         '';
@@ -71,9 +71,9 @@ 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 = ''
+        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
           in the top-level directory. If this is on another filesystem (e.g /var/lib/baseq3) the .pk3 files are searched in
           $baseq3/.q3a/baseq3/
diff --git a/nixos/modules/services/games/teeworlds.nix b/nixos/modules/services/games/teeworlds.nix
index babf989c98ca..ffef440330c4 100644
--- a/nixos/modules/services/games/teeworlds.nix
+++ b/nixos/modules/services/games/teeworlds.nix
@@ -20,18 +20,18 @@ in
 {
   options = {
     services.teeworlds = {
-      enable = mkEnableOption "Teeworlds Server";
+      enable = mkEnableOption (lib.mdDoc "Teeworlds Server");
 
       openPorts = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to open firewall ports for Teeworlds";
+        description = lib.mdDoc "Whether to open firewall ports for Teeworlds";
       };
 
       name = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Name of the server. Defaults to 'unnamed server'.
         '';
       };
@@ -40,7 +40,7 @@ in
         type = types.bool;
         example = true;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether the server registers as public server in the global server list. This is disabled by default because of privacy.
         '';
       };
@@ -48,7 +48,7 @@ in
       motd = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Set the server message of the day text.
         '';
       };
@@ -56,7 +56,7 @@ in
       password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Password to connect to the server.
         '';
       };
@@ -64,15 +64,15 @@ in
       rconPassword = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Password to access the remote console. If not set, a randomly generated one is displayed in the server log.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8303;
-        description = ''
+        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/nixos/modules/services/games/terraria.nix b/nixos/modules/services/games/terraria.nix
index 29f976b3c2ae..ccdd779165b8 100644
--- a/nixos/modules/services/games/terraria.nix
+++ b/nixos/modules/services/games/terraria.nix
@@ -36,16 +36,16 @@ in
       enable = mkOption {
         type        = types.bool;
         default     = false;
-        description = ''
-          If enabled, starts a Terraria server. The server can be connected to via <literal>tmux -S ''${config.${opt.dataDir}}/terraria.sock attach</literal>
-          for administration by users who are a part of the <literal>terraria</literal> group (use <literal>C-b d</literal> shortcut to detach again).
+        description = lib.mdDoc ''
+          If enabled, starts a Terraria server. The server can be connected to via `tmux -S ''${config.${opt.dataDir}}/terraria.sock attach`
+          for administration by users who are a part of the `terraria` group (use `C-b d` shortcut to detach again).
         '';
       };
 
       port = mkOption {
         type        = types.port;
         default     = 7777;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the port to listen on.
         '';
       };
@@ -53,7 +53,7 @@ in
       maxPlayers = mkOption {
         type        = types.ints.u8;
         default     = 255;
-        description = ''
+        description = lib.mdDoc ''
           Sets the max number of players (between 1 and 255).
         '';
       };
@@ -61,15 +61,15 @@ in
       password = mkOption {
         type        = types.nullOr types.str;
         default     = null;
-        description = ''
-          Sets the server password. Leave <literal>null</literal> for no password.
+        description = lib.mdDoc ''
+          Sets the server password. Leave `null` for no password.
         '';
       };
 
       messageOfTheDay = mkOption {
         type        = types.nullOr types.str;
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           Set the server message of the day text.
         '';
       };
@@ -77,18 +77,18 @@ in
       worldPath = mkOption {
         type        = types.nullOr types.path;
         default     = null;
-        description = ''
-          The path to the world file (<literal>.wld</literal>) which should be loaded.
+        description = lib.mdDoc ''
+          The path to the world file (`.wld`) which should be loaded.
           If no world exists at this path, one will be created with the size
-          specified by <literal>autoCreatedWorldSize</literal>.
+          specified by `autoCreatedWorldSize`.
         '';
       };
 
       autoCreatedWorldSize = mkOption {
         type        = types.enum [ "small" "medium" "large" ];
         default     = "medium";
-        description = ''
-          Specifies the size of the auto-created world if <literal>worldPath</literal> does not
+        description = lib.mdDoc ''
+          Specifies the size of the auto-created world if `worldPath` does not
           point to an existing world.
         '';
       };
@@ -96,7 +96,7 @@ in
       banListPath = mkOption {
         type        = types.nullOr types.path;
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to the ban list.
         '';
       };
@@ -104,26 +104,26 @@ in
       secure = mkOption {
         type        = types.bool;
         default     = false;
-        description = "Adds additional cheat protection to the server.";
+        description = lib.mdDoc "Adds additional cheat protection to the server.";
       };
 
       noUPnP = mkOption {
         type        = types.bool;
         default     = false;
-        description = "Disables automatic Universal Plug and Play.";
+        description = lib.mdDoc "Disables automatic Universal Plug and Play.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Wheter to open ports in the firewall";
+        description = lib.mdDoc "Whether to open ports in the firewall";
       };
 
       dataDir = mkOption {
         type        = types.str;
         default     = "/var/lib/terraria";
         example     = "/srv/terraria";
-        description = "Path to variable state data directory for terraria.";
+        description = lib.mdDoc "Path to variable state data directory for terraria.";
       };
     };
   };
@@ -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/nixos/modules/services/hardware/acpid.nix b/nixos/modules/services/hardware/acpid.nix
index 883ef0830037..821f4ef205fc 100644
--- a/nixos/modules/services/hardware/acpid.nix
+++ b/nixos/modules/services/hardware/acpid.nix
@@ -48,12 +48,12 @@ in
 
     services.acpid = {
 
-      enable = mkEnableOption "the ACPI daemon";
+      enable = mkEnableOption (lib.mdDoc "the ACPI daemon");
 
       logEvents = mkOption {
         type = types.bool;
         default = false;
-        description = "Log all event activity.";
+        description = lib.mdDoc "Log all event activity.";
       };
 
       handlers = mkOption {
@@ -62,22 +62,22 @@ in
             event = mkOption {
               type = types.str;
               example = literalExpression ''"button/power.*" "button/lid.*" "ac_adapter.*" "button/mute.*" "button/volumedown.*" "cd/play.*" "cd/next.*"'';
-              description = "Event type.";
+              description = lib.mdDoc "Event type.";
             };
 
             action = mkOption {
               type = types.lines;
-              description = "Shell commands to execute when the event is triggered.";
+              description = lib.mdDoc "Shell commands to execute when the event is triggered.";
             };
           };
         });
 
-        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 = {
@@ -104,19 +104,19 @@ in
       powerEventCommands = mkOption {
         type = types.lines;
         default = "";
-        description = "Shell commands to execute on a button/power.* event.";
+        description = lib.mdDoc "Shell commands to execute on a button/power.* event.";
       };
 
       lidEventCommands = mkOption {
         type = types.lines;
         default = "";
-        description = "Shell commands to execute on a button/lid.* event.";
+        description = lib.mdDoc "Shell commands to execute on a button/lid.* event.";
       };
 
       acEventCommands = mkOption {
         type = types.lines;
         default = "";
-        description = "Shell commands to execute on an ac_adapter.* event.";
+        description = lib.mdDoc "Shell commands to execute on an ac_adapter.* event.";
       };
 
     };
diff --git a/nixos/modules/services/hardware/actkbd.nix b/nixos/modules/services/hardware/actkbd.nix
index b499de97b2c3..1718d179bf5e 100644
--- a/nixos/modules/services/hardware/actkbd.nix
+++ b/nixos/modules/services/hardware/actkbd.nix
@@ -20,25 +20,25 @@ let
 
       keys = mkOption {
         type = types.listOf types.int;
-        description = "List of keycodes to match.";
+        description = lib.mdDoc "List of keycodes to match.";
       };
 
       events = mkOption {
         type = types.listOf (types.enum ["key" "rep" "rel"]);
         default = [ "key" ];
-        description = "List of events to match.";
+        description = lib.mdDoc "List of events to match.";
       };
 
       attributes = mkOption {
         type = types.listOf types.str;
         default = [ "exec" ];
-        description = "List of attributes.";
+        description = lib.mdDoc "List of attributes.";
       };
 
       command = mkOption {
         type = types.str;
         default = "";
-        description = "What to run.";
+        description = lib.mdDoc "What to run.";
       };
 
     };
@@ -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.
@@ -78,19 +78,19 @@ in
           [ { keys = [ 113 ]; events = [ "key" ]; command = "''${pkgs.alsa-utils}/bin/amixer -q set Master toggle"; }
           ]
         '';
-        description = ''
-          Key bindings for <command>actkbd</command>.
+        description = lib.mdDoc ''
+          Key bindings for {command}`actkbd`.
 
-          See <command>actkbd</command> <filename>README</filename> for documentation.
+          See {command}`actkbd` {file}`README` for documentation.
 
-          The example shows a piece of what <option>sound.mediaKeys.enable</option> does when enabled.
+          The example shows a piece of what {option}`sound.mediaKeys.enable` does when enabled.
         '';
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Literal contents to append to the end of actkbd configuration file.
         '';
       };
diff --git a/nixos/modules/services/hardware/argonone.nix b/nixos/modules/services/hardware/argonone.nix
new file mode 100644
index 000000000000..e67c2625062e
--- /dev/null
+++ b/nixos/modules/services/hardware/argonone.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.hardware.argonone;
+in
+{
+  options.services.hardware.argonone = {
+    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 = lib.literalExpression "pkgs.argononed";
+      description = lib.mdDoc ''
+        The package implementing the Argon One driver
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    hardware.i2c.enable = true;
+    hardware.deviceTree.overlays = [
+      {
+        name = "argononed";
+        dtboFile = "${cfg.package}/boot/overlays/argonone.dtbo";
+      }
+      {
+        name = "i2c1-okay-overlay";
+        dtsText = ''
+          /dts-v1/;
+          /plugin/;
+          / {
+            compatible = "brcm,bcm2711";
+            fragment@0 {
+              target = <&i2c1>;
+              __overlay__ {
+                status = "okay";
+              };
+            };
+          };
+        '';
+      }
+    ];
+    environment.systemPackages = [ cfg.package ];
+    systemd.services.argononed = {
+      description = "Argon One Raspberry Pi case Daemon Service";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${cfg.package}/bin/argononed";
+        PIDFile = "/run/argononed.pid";
+        Restart = "on-failure";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ misterio77 ];
+
+}
diff --git a/nixos/modules/services/hardware/asusd.nix b/nixos/modules/services/hardware/asusd.nix
new file mode 100644
index 000000000000..fba9b059bbbb
--- /dev/null
+++ b/nixos/modules/services/hardware/asusd.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.asusd;
+  json = pkgs.formats.json { };
+  toml = pkgs.formats.toml { };
+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 = json.type;
+        default = { };
+        description = lib.mdDoc ''
+          The content of /etc/asusd/anime.conf.
+          See https://asus-linux.org/asusctl/#anime-control.
+        '';
+      };
+
+      asusdConfig = lib.mkOption {
+        type = json.type;
+        default = { };
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd.conf.
+          See https://asus-linux.org/asusctl/.
+        '';
+      };
+
+      auraConfig = lib.mkOption {
+        type = json.type;
+        default = { };
+        description = lib.mdDoc ''
+          The content of /etc/asusd/aura.conf.
+          See https://asus-linux.org/asusctl/#led-keyboard-control.
+        '';
+      };
+
+      profileConfig = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = "";
+        description = lib.mdDoc ''
+          The content of /etc/asusd/profile.conf.
+          See https://asus-linux.org/asusctl/#profiles.
+        '';
+      };
+
+      ledModesConfig = lib.mkOption {
+        type = lib.types.nullOr toml.type;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd-ledmodes.toml. Leave `null` to use default settings.
+          See https://asus-linux.org/asusctl/#led-keyboard-control.
+        '';
+      };
+
+      userLedModesConfig = lib.mkOption {
+        type = lib.types.nullOr toml.type;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd-user-ledmodes.toml.
+          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 != { }) {
+          source = json.generate name cfg;
+          mode = "0644";
+        };
+      in
+      {
+        "asusd/anime.conf" = maybeConfig "anime.conf" cfg.animeConfig;
+        "asusd/asusd.conf" = maybeConfig "asusd.conf" cfg.asusdConfig;
+        "asusd/aura.conf" = maybeConfig "aura.conf" cfg.auraConfig;
+        "asusd/profile.conf" = lib.mkIf (cfg.profileConfig != null) {
+          source = pkgs.writeText "profile.conf" cfg.profileConfig;
+          mode = "0644";
+        };
+        "asusd/asusd-ledmodes.toml" = {
+          source =
+            if cfg.ledModesConfig == null
+            then "${pkgs.asusctl}/share/asusd/data/asusd-ledmodes.toml"
+            else toml.generate "asusd-ledmodes.toml" cfg.ledModesConfig;
+          mode = "0644";
+        };
+      };
+
+    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/nixos/modules/services/hardware/auto-cpufreq.nix b/nixos/modules/services/hardware/auto-cpufreq.nix
index f846476b30b5..9698e72eb31e 100644
--- a/nixos/modules/services/hardware/auto-cpufreq.nix
+++ b/nixos/modules/services/hardware/auto-cpufreq.nix
@@ -5,7 +5,7 @@ let
 in {
   options = {
     services.auto-cpufreq = {
-      enable = mkEnableOption "auto-cpufreq daemon";
+      enable = mkEnableOption (lib.mdDoc "auto-cpufreq daemon");
     };
   };
 
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index 69a66723e76c..6453e6968dcc 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -36,35 +36,29 @@ 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;
         default = true;
-        description = "Whether to power up the default Bluetooth controller on boot.";
+        description = lib.mdDoc "Whether to power up the default Bluetooth controller on boot.";
       };
 
       package = mkOption {
         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>
         '';
       };
 
       disabledPlugins = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = "Built-in plugins to disable";
+        description = lib.mdDoc "Built-in plugins to disable";
       };
 
       settings = mkOption {
@@ -75,7 +69,7 @@ in
             ControllerMode = "bredr";
           };
         };
-        description = "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
+        description = lib.mdDoc "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
       };
     };
   };
diff --git a/nixos/modules/services/hardware/bolt.nix b/nixos/modules/services/hardware/bolt.nix
index 32b60af06037..6990a9ea63b3 100644
--- a/nixos/modules/services/hardware/bolt.nix
+++ b/nixos/modules/services/hardware/bolt.nix
@@ -12,7 +12,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Bolt, a userspace daemon to enable
           security levels for Thunderbolt 3 on GNU/Linux.
 
diff --git a/nixos/modules/services/hardware/brltty.nix b/nixos/modules/services/hardware/brltty.nix
index 730560175327..3133804f485f 100644
--- a/nixos/modules/services/hardware/brltty.nix
+++ b/nixos/modules/services/hardware/brltty.nix
@@ -25,7 +25,7 @@ in {
     services.brltty.enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Whether to enable the BRLTTY daemon.";
+      description = lib.mdDoc "Whether to enable the BRLTTY daemon.";
     };
 
   };
diff --git a/nixos/modules/services/hardware/ddccontrol.nix b/nixos/modules/services/hardware/ddccontrol.nix
index f0b5a9c81960..0f1e8bf0d26c 100644
--- a/nixos/modules/services/hardware/ddccontrol.nix
+++ b/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/nixos/modules/services/hardware/fancontrol.nix b/nixos/modules/services/hardware/fancontrol.nix
index 861b70970b87..e7eb8ebf92be 100644
--- a/nixos/modules/services/hardware/fancontrol.nix
+++ b/nixos/modules/services/hardware/fancontrol.nix
@@ -9,11 +9,11 @@ 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;
-      description = "Required fancontrol configuration file content. See <citerefentry><refentrytitle>pwmconfig</refentrytitle><manvolnum>8</manvolnum></citerefentry> from the lm_sensors package.";
+      description = lib.mdDoc "Required fancontrol configuration file content. See {manpage}`pwmconfig(8)` from the lm_sensors package.";
       example = ''
         # Configuration file generated by pwmconfig
         INTERVAL=10
diff --git a/nixos/modules/services/hardware/freefall.nix b/nixos/modules/services/hardware/freefall.nix
index 3f7b15924496..7b794264ff35 100644
--- a/nixos/modules/services/hardware/freefall.nix
+++ b/nixos/modules/services/hardware/freefall.nix
@@ -13,7 +13,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to protect HP/Dell laptop hard drives (not SSDs) in free fall.
       '';
     };
@@ -22,7 +22,7 @@ in {
       type = types.package;
       default = pkgs.freefall;
       defaultText = literalExpression "pkgs.freefall";
-      description = ''
+      description = lib.mdDoc ''
         freefall derivation to use.
       '';
     };
@@ -30,7 +30,7 @@ in {
     devices = mkOption {
       type = types.listOf types.str;
       default = [ "/dev/sda" ];
-      description = ''
+      description = lib.mdDoc ''
         Device paths to all internal spinning hard drives.
       '';
     };
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index e0506416ffa3..8d7651f97c39 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -7,17 +7,20 @@ 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]
+    "fwupd/uefi_capsule.conf" = {
+      source = pkgs.writeText "uefi_capsule.conf" ''
+        [uefi_capsule]
         OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
       '';
     };
@@ -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
@@ -53,45 +64,36 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable fwupd, a DBus service that allows
           applications to update firmware.
         '';
       };
 
-      disabledDevices = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
-        description = ''
-          Allow disabling specific devices by their GUID
-        '';
-      };
-
-      disabledPlugins = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "udev" ];
-        description = ''
-          Allow disabling specific plugins
-        '';
-      };
-
       extraTrustedKeys = mkOption {
         type = types.listOf types.path;
         default = [];
         example = literalExpression "[ /etc/nixos/fwupd/myfirmware.pem ]";
-        description = ''
+        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.
         '';
       };
 
+      extraRemotes = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = [ "lvfs-testing" ];
+        description = lib.mdDoc ''
+          Enables extra remotes in fwupd. See `/etc/fwupd/remotes.d`.
+        '';
+      };
+
       enableTestRemote = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable test remote. This is used by
-          <link xlink:href="https://github.com/fwupd/fwupd/blob/master/data/installed-tests/README.md">installed tests</link>.
+          [installed tests](https://github.com/fwupd/fwupd/blob/master/data/installed-tests/README.md).
         '';
       };
 
@@ -99,33 +101,66 @@ in {
         type = types.package;
         default = pkgs.fwupd;
         defaultText = literalExpression "pkgs.fwupd";
-        description = ''
+        description = lib.mdDoc ''
           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.
+              '';
+            };
+          };
+        };
+        default = {};
+        description = lib.mdDoc ''
+          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;
 
     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 ];
 
     systemd.packages = [ cfg.package ];
+
+    security.polkit.enable = true;
   };
 
   meta = {
diff --git a/nixos/modules/services/hardware/illum.nix b/nixos/modules/services/hardware/illum.nix
index ff73c99a6537..46172fb7b53a 100644
--- a/nixos/modules/services/hardware/illum.nix
+++ b/nixos/modules/services/hardware/illum.nix
@@ -13,7 +13,7 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable illum, a daemon for controlling screen brightness with brightness buttons.
         '';
       };
@@ -28,6 +28,7 @@ in {
       description = "Backlight Adjustment Service";
       wantedBy = [ "multi-user.target" ];
       serviceConfig.ExecStart = "${pkgs.illum}/bin/illum-d";
+      serviceConfig.Restart = "on-failure";
     };
 
   };
diff --git a/nixos/modules/services/hardware/interception-tools.nix b/nixos/modules/services/hardware/interception-tools.nix
index e69c05841ee0..4f86bd470ea7 100644
--- a/nixos/modules/services/hardware/interception-tools.nix
+++ b/nixos/modules/services/hardware/interception-tools.nix
@@ -9,14 +9,14 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Whether to enable the interception tools service.";
+      description = lib.mdDoc "Whether to enable the interception tools service.";
     };
 
     plugins = mkOption {
       type = types.listOf types.package;
       default = [ pkgs.interception-tools-plugins.caps2esc ];
       defaultText = literalExpression "[ pkgs.interception-tools-plugins.caps2esc ]";
-      description = ''
+      description = lib.mdDoc ''
         A list of interception tools plugins that will be made available to use
         inside the udevmon configuration.
       '';
@@ -36,7 +36,7 @@ in {
             EVENTS:
               EV_KEY: [KEY_X, KEY_Y]
       '';
-      description = ''
+      description = lib.mdDoc ''
         String of udevmon YAML configuration, or path to a udevmon YAML
         configuration file.
       '';
diff --git a/nixos/modules/services/hardware/irqbalance.nix b/nixos/modules/services/hardware/irqbalance.nix
index c79e0eb83ece..8ba0a73d895d 100644
--- a/nixos/modules/services/hardware/irqbalance.nix
+++ b/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/nixos/modules/services/hardware/joycond.nix b/nixos/modules/services/hardware/joycond.nix
index d81c1bb6d63d..1af18b3b63d3 100644
--- a/nixos/modules/services/hardware/joycond.nix
+++ b/nixos/modules/services/hardware/joycond.nix
@@ -9,13 +9,13 @@ 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";
-      description = ''
+      defaultText = lib.literalExpression "pkgs.joycond";
+      description = lib.mdDoc ''
         The joycond package to use.
       '';
     };
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
new file mode 100644
index 000000000000..84265eb8f947
--- /dev/null
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -0,0 +1,215 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kanata;
+
+  keyboard = {
+    options = {
+      devices = mkOption {
+        type = types.addCheck (types.listOf types.str)
+          (devices: (length devices) > 0);
+        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.
+          :::
+        '';
+      };
+      config = mkOption {
+        type = types.lines;
+        example = ''
+          (defsrc
+            grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
+            tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
+            caps a    s    d    f    g    h    j    k    l    ;    '    ret
+            lsft z    x    c    v    b    n    m    ,    .    /    rsft
+            lctl lmet lalt           spc            ralt rmet rctl)
+
+          (deflayer qwerty
+            grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
+            tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
+            @cap a    s    d    f    g    h    j    k    l    ;    '    ret
+            lsft z    x    c    v    b    n    m    ,    .    /    rsft
+            lctl lmet lalt           spc            ralt rmet rctl)
+
+          (defalias
+            ;; tap within 100ms for capslk, hold more than 100ms for lctl
+            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.
+        '';
+      };
+      extraDefCfg = mkOption {
+        type = types.lines;
+        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.
+        '';
+      };
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = mdDoc "Extra command line arguments passed to kanata.";
+      };
+      port = mkOption {
+        type = types.nullOr types.port;
+        default = null;
+        example = 6666;
+        description = mdDoc ''
+          Port to run the notification server on. `null` will not run the
+          server.
+        '';
+      };
+    };
+  };
+
+  mkName = name: "kanata-${name}";
+
+  mkDevices = devices: concatStringsSep ":" devices;
+
+  mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
+    (defcfg
+      ${keyboard.extraDefCfg}
+      linux-dev ${mkDevices keyboard.devices})
+
+    ${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;
+
+    serviceConfig = {
+      ExecStart = ''
+        ${cfg.package}/bin/kanata \
+          --cfg ${mkConfig name keyboard} \
+          --symlink-path ''${RUNTIME_DIRECTORY}/${name} \
+          ${optionalString (keyboard.port != null) "--port ${toString keyboard.port}"} \
+          ${utils.escapeSystemdExecArgs keyboard.extraArgs}
+      '';
+
+      DynamicUser = true;
+      RuntimeDirectory = mkName name;
+      SupplementaryGroups = with config.users.groups; [
+        input.name
+        uinput.name
+      ];
+
+      # hardening
+      DeviceAllow = [
+        "/dev/uinput rw"
+        "char-input r"
+      ];
+      CapabilityBoundingSet = [ "" ];
+      DevicePolicy = "closed";
+      IPAddressAllow = optional (keyboard.port != null) "localhost";
+      IPAddressDeny = [ "any" ];
+      LockPersonality = true;
+      MemoryDenyWriteExecute = true;
+      PrivateNetwork = keyboard.port == null;
+      PrivateUsers = true;
+      ProcSubset = "pid";
+      ProtectClock = true;
+      ProtectControlGroups = true;
+      ProtectHome = true;
+      ProtectHostname = true;
+      ProtectKernelLogs = true;
+      ProtectKernelModules = true;
+      ProtectKernelTunables = true;
+      ProtectProc = "invisible";
+      RestrictAddressFamilies =
+        if (keyboard.port == null) then "none" else [ "AF_INET" ];
+      RestrictNamespaces = true;
+      RestrictRealtime = true;
+      SystemCallArchitectures = [ "native" ];
+      SystemCallFilter = [
+        "@system-service"
+        "~@privileged"
+        "~@resources"
+      ];
+      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 (lib.mdDoc "kanata");
+    package = mkOption {
+      type = types.package;
+      default = pkgs.kanata;
+      defaultText = literalExpression "pkgs.kanata";
+      example = literalExpression "pkgs.kanata-with-cmd";
+      description = mdDoc ''
+        The kanata package to use.
+
+        ::: {.note}
+        If `danger-enable-cmd` is enabled in any of the keyboards, the
+        `kanata-with-cmd` package should be used.
+        :::
+      '';
+    };
+    keyboards = mkOption {
+      type = types.attrsOf (types.submodule keyboard);
+      default = { };
+      description = mdDoc "Keyboard configurations.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    hardware.uinput.enable = true;
+
+    systemd = {
+      paths = trivial.pipe cfg.keyboards [
+        (mapAttrsToList mkPaths)
+        concatLists
+        listToAttrs
+      ];
+      services = mapAttrs' mkService cfg.keyboards;
+    };
+  };
+
+  meta.maintainers = with maintainers; [ linj ];
+}
diff --git a/nixos/modules/services/hardware/lcd.nix b/nixos/modules/services/hardware/lcd.nix
index dc8595ea60cd..8d682d137f44 100644
--- a/nixos/modules/services/hardware/lcd.nix
+++ b/nixos/modules/services/hardware/lcd.nix
@@ -36,49 +36,46 @@ in with lib; {
       serverHost = mkOption {
         type = str;
         default = "localhost";
-        description = "Host on which LCDd is listening.";
+        description = lib.mdDoc "Host on which LCDd is listening.";
       };
 
       serverPort = mkOption {
         type = int;
         default = 13666;
-        description = "Port on which LCDd is listening.";
+        description = lib.mdDoc "Port on which LCDd is listening.";
       };
 
       server = {
         enable = mkOption {
           type = bool;
           default = false;
-          description = "Enable the LCD panel server (LCDd)";
+          description = lib.mdDoc "Enable the LCD panel server (LCDd)";
         };
 
         openPorts = mkOption {
           type = bool;
           default = false;
-          description = "Open the ports in the firewall";
+          description = lib.mdDoc "Open the ports in the firewall";
         };
 
         usbPermissions = mkOption {
           type = bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Set group-write permissions on a USB device.
-            </para>
-            <para>
+
             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:
-            </para>
-            <para>
-            <literal>
+
+            ```
             Bus 005 Device 002: ID 0403:c630 Future Technology Devices International, Ltd lcd2usb interface
-            </literal>
-            </para>
-            <para>
+            ```
+
             In this case the vendor id is 0403 and the product id is c630.
           '';
         };
@@ -86,25 +83,25 @@ in with lib; {
         usbVid = mkOption {
           type = str;
           default = "";
-          description = "The vendor ID of the USB device to claim.";
+          description = lib.mdDoc "The vendor ID of the USB device to claim.";
         };
 
         usbPid = mkOption {
           type = str;
           default = "";
-          description = "The product ID of the USB device to claim.";
+          description = lib.mdDoc "The product ID of the USB device to claim.";
         };
 
         usbGroup = mkOption {
           type = str;
           default = "dialout";
-          description = "The group to use for settings permissions. This group must exist or you will have to create it.";
+          description = lib.mdDoc "The group to use for settings permissions. This group must exist or you will have to create it.";
         };
 
         extraConfig = mkOption {
           type = lines;
           default = "";
-          description = "Additional configuration added verbatim to the server config.";
+          description = lib.mdDoc "Additional configuration added verbatim to the server config.";
         };
       };
 
@@ -112,19 +109,19 @@ in with lib; {
         enable = mkOption {
           type = bool;
           default = false;
-          description = "Enable the LCD panel client (LCDproc)";
+          description = lib.mdDoc "Enable the LCD panel client (LCDproc)";
         };
 
         extraConfig = mkOption {
           type = lines;
           default = "";
-          description = "Additional configuration added verbatim to the client config.";
+          description = lib.mdDoc "Additional configuration added verbatim to the client config.";
         };
 
         restartForever = mkOption {
           type = bool;
           default = true;
-          description = "Try restarting the client forever.";
+          description = lib.mdDoc "Try restarting the client forever.";
         };
       };
     };
diff --git a/nixos/modules/services/hardware/lirc.nix b/nixos/modules/services/hardware/lirc.nix
index f970b0a095c3..5b1a8d10c729 100644
--- a/nixos/modules/services/hardware/lirc.nix
+++ b/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,18 +19,18 @@ in {
           [lircd]
           nodaemon = False
         '';
-        description = "LIRC default options descriped in man:lircd(8) (<filename>lirc_options.conf</filename>)";
+        description = lib.mdDoc "LIRC default options described in man:lircd(8) ({file}`lirc_options.conf`)";
       };
 
       configs = mkOption {
         type = types.listOf types.lines;
-        description = "Configurations for lircd to load, see man:lircd.conf(5) for details (<filename>lircd.conf</filename>)";
+        description = lib.mdDoc "Configurations for lircd to load, see man:lircd.conf(5) for details ({file}`lircd.conf`)";
       };
 
       extraArguments = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Extra arguments to lircd.";
+        description = lib.mdDoc "Extra arguments to lircd.";
       };
     };
   };
diff --git a/nixos/modules/services/hardware/nvidia-optimus.nix b/nixos/modules/services/hardware/nvidia-optimus.nix
index d53175052c74..5b5273ed7823 100644
--- a/nixos/modules/services/hardware/nvidia-optimus.nix
+++ b/nixos/modules/services/hardware/nvidia-optimus.nix
@@ -11,7 +11,7 @@ let kernel = config.boot.kernelPackages; in
     hardware.nvidiaOptimus.disable = lib.mkOption {
       default = false;
       type = lib.types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Completely disable the NVIDIA graphics card and use the
         integrated graphics processor instead.
       '';
diff --git a/nixos/modules/services/hardware/openrgb.nix b/nixos/modules/services/hardware/openrgb.nix
new file mode 100644
index 000000000000..12438f01e524
--- /dev/null
+++ b/nixos/modules/services/hardware/openrgb.nix
@@ -0,0 +1,52 @@
+{ 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-piix" ]
+     ++ lib.optionals (cfg.motherboard == "intel") [ "i2c-i801" ];
+
+    systemd.services.openrgb = {
+      description = "OpenRGB server daemon";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/openrgb --server --server-port ${toString cfg.server.port}";
+        Restart = "always";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ jonringer ];
+}
diff --git a/nixos/modules/services/hardware/pcscd.nix b/nixos/modules/services/hardware/pcscd.nix
index b1a5c680a022..a09c64645c48 100644
--- a/nixos/modules/services/hardware/pcscd.nix
+++ b/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,14 +20,14 @@ in
   ###### interface
 
   options.services.pcscd = {
-    enable = mkEnableOption "PCSC-Lite daemon";
+    enable = mkEnableOption (lib.mdDoc "PCSC-Lite daemon");
 
     plugins = mkOption {
       type = types.listOf types.package;
       default = [ pkgs.ccid ];
       defaultText = literalExpression "[ pkgs.ccid ]";
       example = literalExpression "[ pkgs.pcsc-cyberjack ]";
-      description = "Plugin packages to be used for PCSC-Lite.";
+      description = lib.mdDoc "Plugin packages to be used for PCSC-Lite.";
     };
 
     readerConfig = mkOption {
@@ -35,11 +39,10 @@ in
         LIBPATH           /path/to/serial_reader.so
         CHANNELID         1
       '';
-      description = ''
+      description = lib.mdDoc ''
         Configuration for devices that aren't hotpluggable.
 
-        See <citerefentry><refentrytitle>reader.conf</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for valid options.
+        See {manpage}`reader.conf(5)` for valid options.
       '';
     };
   };
@@ -50,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" ];
 
@@ -67,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/nixos/modules/services/hardware/pommed.nix b/nixos/modules/services/hardware/pommed.nix
index bf7d6a46a293..a71004c1767c 100644
--- a/nixos/modules/services/hardware/pommed.nix
+++ b/nixos/modules/services/hardware/pommed.nix
@@ -13,7 +13,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use the pommed tool to handle Apple laptop
           keyboard hotkeys.
         '';
@@ -22,12 +22,12 @@ in {
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
-          The path to the <filename>pommed.conf</filename> file. Leave
+        description = lib.mdDoc ''
+          The path to the {file}`pommed.conf` file. Leave
           to null to use the default config file
-          (<filename>/etc/pommed.conf.mactel</filename>). See the
-          files <filename>/etc/pommed.conf.mactel</filename> and
-          <filename>/etc/pommed.conf.pmac</filename> for examples to
+          ({file}`/etc/pommed.conf.mactel`). See the
+          files {file}`/etc/pommed.conf.mactel` and
+          {file}`/etc/pommed.conf.pmac` for examples to
           build on.
         '';
       };
diff --git a/nixos/modules/services/hardware/power-profiles-daemon.nix b/nixos/modules/services/hardware/power-profiles-daemon.nix
index 4144bc667088..101da01b4a71 100644
--- a/nixos/modules/services/hardware/power-profiles-daemon.nix
+++ b/nixos/modules/services/hardware/power-profiles-daemon.nix
@@ -18,7 +18,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable power-profiles-daemon, a DBus daemon that allows
           changing system behavior based upon user-selected power profiles.
         '';
diff --git a/nixos/modules/services/hardware/rasdaemon.nix b/nixos/modules/services/hardware/rasdaemon.nix
index 2d4c6d2ce959..a1334684b7d5 100644
--- a/nixos/modules/services/hardware/rasdaemon.nix
+++ b/nixos/modules/services/hardware/rasdaemon.nix
@@ -10,18 +10,18 @@ in
 {
   options.hardware.rasdaemon = {
 
-    enable = mkEnableOption "RAS logging daemon";
+    enable = mkEnableOption (lib.mdDoc "RAS logging daemon");
 
     record = mkOption {
       type = types.bool;
       default = true;
-      description = "record events via sqlite3, required for ras-mc-ctl";
+      description = lib.mdDoc "record events via sqlite3, required for ras-mc-ctl";
     };
 
     mainboard = mkOption {
       type = types.lines;
       default = "";
-      description = "Custom mainboard description, see <citerefentry><refentrytitle>ras-mc-ctl</refentrytitle><manvolnum>8</manvolnum></citerefentry> for more details.";
+      description = lib.mdDoc "Custom mainboard description, see {manpage}`ras-mc-ctl(8)` for more details.";
       example = ''
         vendor = ASRock
         model = B450M Pro4
@@ -40,7 +40,7 @@ in
     labels = mkOption {
       type = types.lines;
       default = "";
-      description = "Additional memory module label descriptions to be placed in /etc/ras/dimm_labels.d/labels";
+      description = lib.mdDoc "Additional memory module label descriptions to be placed in /etc/ras/dimm_labels.d/labels";
       example = ''
         # vendor and model may be shown by 'ras-mc-ctl --mainboard'
         vendor: ASRock
@@ -57,7 +57,7 @@ in
     config = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         rasdaemon configuration, currently only used for CE PFA
         for details, read rasdaemon.outPath/etc/sysconfig/rasdaemon's comments
       '';
@@ -72,11 +72,11 @@ in
     extraModules = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = "extra kernel modules to load";
+      description = lib.mdDoc "extra kernel modules to load";
       example = [ "i7core_edac" ];
     };
 
-    testing = mkEnableOption "error injection infrastructure";
+    testing = mkEnableOption (lib.mdDoc "error injection infrastructure");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/hardware/ratbagd.nix b/nixos/modules/services/hardware/ratbagd.nix
index 01a8276750f2..c939d5e40a24 100644
--- a/nixos/modules/services/hardware/ratbagd.nix
+++ b/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/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
index caf232e234eb..60354c7644f7 100644
--- a/nixos/modules/services/hardware/sane.nix
+++ b/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.d";
+    LD_LIBRARY_PATH = [ "/etc/sane-libs" ];
   };
 
   backends = [ pkg netConf ] ++ optional config.services.saned.enable sanedConf ++ config.hardware.sane.extraBackends;
@@ -48,55 +48,57 @@ 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.
+        :::
       '';
     };
 
     hardware.sane.snapshot = mkOption {
       type = types.bool;
       default = false;
-      description = "Use a development snapshot of SANE scanner drivers.";
+      description = lib.mdDoc "Use a development snapshot of SANE scanner drivers.";
     };
 
     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 {
       type = types.listOf types.str;
       default = [];
       example = [ "v4l" ];
-      description = ''
+      description = lib.mdDoc ''
         Names of backends which are enabled by default but should be disabled.
-        See <literal>$SANE_CONFIG_DIR/dll.conf</literal> for the list of possible names.
+        See `$SANE_CONFIG_DIR/dll.conf` for the list of possible names.
       '';
     };
 
     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 {
       type = types.lines;
       default = "";
       example = "192.168.0.16";
-      description = ''
+      description = lib.mdDoc ''
         Network hosts that should be probed for remote scanners.
       '';
     };
@@ -105,7 +107,7 @@ in
       type = types.bool;
       default = false;
       example = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable drivers for the Fujitsu ScanSnap scanners.
 
         The driver files are unfree and extracted from the Windows driver image.
@@ -116,22 +118,31 @@ in
       type = types.package;
       default = pkgs.sane-drivers.epjitsu;
       defaultText = literalExpression "pkgs.sane-drivers.epjitsu";
-      description = ''
+      description = lib.mdDoc ''
         Epjitsu driver package to use. Useful if you want to extract the driver files yourself.
 
-        The process is described in the <literal>/etc/sane.d/epjitsu.conf</literal> file in
-        the <literal>sane-backends</literal> package.
+        The process is described in the `/etc/sane.d/epjitsu.conf` file in
+        the `sane-backends` package.
+      '';
+    };
+
+    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 = ''
+      description = lib.mdDoc ''
         Enable saned network daemon for remote connection to scanners.
 
-        saned would be runned from <literal>scanner</literal> user; to allow
-        access to hardware that doesn't have <literal>scanner</literal> group
+        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.
       '';
     };
@@ -140,7 +151,7 @@ in
       type = types.lines;
       default = "";
       example = "192.168.0.0/24";
-      description = ''
+      description = lib.mdDoc ''
         Extra saned configuration lines.
       '';
     };
@@ -156,9 +167,12 @@ in
 
       environment.systemPackages = backends;
       environment.sessionVariables = env;
+      environment.etc."sane.d".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/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
index 8f9998108406..e737a4ce20de 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
+++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
@@ -15,7 +15,7 @@ let
 
       name = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The friendly name you give to the network device. If undefined,
           the name of attribute will be used.
         '';
@@ -25,7 +25,7 @@ let
 
       model = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The model of the network device.
         '';
 
@@ -35,7 +35,7 @@ let
       ip = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The ip address of the device. If undefined, you will have to
           provide a nodename.
         '';
@@ -46,7 +46,7 @@ let
       nodename = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The node name of the device. If undefined, you will have to
           provide an ip.
         '';
@@ -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.
       '';
@@ -82,7 +82,7 @@ in
           office2 = { model = "MFC-7860DW"; nodename = "BRW0080927AFBCE"; };
         };
       type = with types; attrsOf (submodule netDeviceOpts);
-      description = ''
+      description = lib.mdDoc ''
         The list of network devices that will be registered against the brscan4
         sane backend.
       '';
diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
index 9d083a615a2c..f76ab701c5b9 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
+++ b/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/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
index 2e4ad8cc3ba0..d29e0f542f55 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
+++ b/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix
@@ -15,7 +15,7 @@ let
 
       name = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The friendly name you give to the network device. If undefined,
           the name of attribute will be used.
         '';
@@ -25,7 +25,7 @@ let
 
       model = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The model of the network device.
         '';
 
@@ -35,7 +35,7 @@ let
       ip = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The ip address of the device. If undefined, you will have to
           provide a nodename.
         '';
@@ -46,7 +46,7 @@ let
       nodename = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The node name of the device. If undefined, you will have to
           provide an ip.
         '';
@@ -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 = {};
@@ -77,7 +77,7 @@ in
           office2 = { model = "MFC-7860DW"; nodename = "BRW0080927AFBCE"; };
         };
       type = with types; attrsOf (submodule netDeviceOpts);
-      description = ''
+      description = lib.mdDoc ''
         The list of network devices that will be registered against the brscan5
         sane backend.
       '';
diff --git a/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix b/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
index d71a17f5ea6b..5b05694abc01 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
+++ b/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/nixos/modules/services/hardware/spacenavd.nix b/nixos/modules/services/hardware/spacenavd.nix
index 69ca6f102efe..36f132439377 100644
--- a/nixos/modules/services/hardware/spacenavd.nix
+++ b/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/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix
new file mode 100644
index 000000000000..cb604db91dc3
--- /dev/null
+++ b/nixos/modules/services/hardware/supergfxd.nix
@@ -0,0 +1,38 @@
+{ 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; };
+
+    services.dbus.enable = true;
+
+    systemd.packages = [ pkgs.supergfxctl ];
+    systemd.services.supergfxd.wantedBy = [ "multi-user.target" ];
+
+    services.dbus.packages = [ pkgs.supergfxctl ];
+    services.udev.packages = [ pkgs.supergfxctl ];
+  };
+
+  meta.maintainers = pkgs.supergfxctl.meta.maintainers;
+}
diff --git a/nixos/modules/services/hardware/tcsd.nix b/nixos/modules/services/hardware/tcsd.nix
index e414b9647c9b..f22924d410d5 100644
--- a/nixos/modules/services/hardware/tcsd.nix
+++ b/nixos/modules/services/hardware/tcsd.nix
@@ -40,7 +40,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable tcsd, a Trusted Computing management service
           that provides TCG Software Stack (TSS).  The tcsd daemon is
           the only portal to the Trusted Platform Module (TPM), a hardware
@@ -51,19 +51,19 @@ in
       user = mkOption {
         default = "tss";
         type = types.str;
-        description = "User account under which tcsd runs.";
+        description = lib.mdDoc "User account under which tcsd runs.";
       };
 
       group = mkOption {
         default = "tss";
         type = types.str;
-        description = "Group account under which tcsd runs.";
+        description = lib.mdDoc "Group account under which tcsd runs.";
       };
 
       stateDir = mkOption {
         default = "/var/lib/tpm";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The location of the system persistent storage file.
           The system persistent storage file holds keys and data across
           restarts of the TCSD and system reboots.
@@ -73,20 +73,20 @@ in
       firmwarePCRs = mkOption {
         default = "0,1,2,3,4,5,6,7";
         type = types.str;
-        description = "PCR indices used in the TPM for firmware measurements.";
+        description = lib.mdDoc "PCR indices used in the TPM for firmware measurements.";
       };
 
       kernelPCRs = mkOption {
         default = "8,9,10,11,12";
         type = types.str;
-        description = "PCR indices used in the TPM for kernel measurements.";
+        description = lib.mdDoc "PCR indices used in the TPM for kernel measurements.";
       };
 
       platformCred = mkOption {
         default = "${cfg.stateDir}/platform.cert";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/platform.cert"'';
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to the platform credential for your TPM. Your TPM
           manufacturer may have provided you with a set of credentials
           (certificates) that should be used when creating identities
@@ -100,7 +100,7 @@ in
         default = "${cfg.stateDir}/conformance.cert";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/conformance.cert"'';
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to the conformance credential for your TPM.
           See also the platformCred option'';
       };
@@ -109,7 +109,7 @@ in
         default = "${cfg.stateDir}/endorsement.cert";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/endorsement.cert"'';
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to the endorsement credential for your TPM.
           See also the platformCred option'';
       };
diff --git a/nixos/modules/services/hardware/thermald.nix b/nixos/modules/services/hardware/thermald.nix
index fcd02ea90c6c..6b694ede5885 100644
--- a/nixos/modules/services/hardware/thermald.nix
+++ b/nixos/modules/services/hardware/thermald.nix
@@ -9,12 +9,12 @@ 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;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable debug logging.
         '';
       };
@@ -22,14 +22,14 @@ in
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "the thermald manual configuration file.";
+        description = lib.mdDoc "the thermald manual configuration file.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.thermald;
         defaultText = literalExpression "pkgs.thermald";
-        description = "Which thermald package to use.";
+        description = lib.mdDoc "Which thermald package to use.";
       };
     };
   };
diff --git a/nixos/modules/services/hardware/thinkfan.nix b/nixos/modules/services/hardware/thinkfan.nix
index 4ea829e496e8..8fa7b456f20e 100644
--- a/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixos/modules/services/hardware/thinkfan.nix
@@ -29,45 +29,47 @@ let
     options = {
       type = mkOption {
         type = types.enum [ "hwmon" "atasmart" "tpacpi" "nvml" ];
-        description = ''
+        description = lib.mdDoc ''
           The ${name} type, can be
-          <literal>hwmon</literal> for standard ${name}s,
+          `hwmon` for standard ${name}s,
 
-          <literal>atasmart</literal> to read the temperature via
+          `atasmart` to read the temperature via
           S.M.A.R.T (requires smartSupport to be enabled),
 
-          <literal>tpacpi</literal> for the legacy thinkpac_acpi driver, or
+          `tpacpi` for the legacy thinkpac_acpi driver, or
 
-          <literal>nvml</literal> for the (proprietary) nVidia driver.
+          `nvml` for the (proprietary) nVidia driver.
         '';
       };
       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") {
       correction = mkOption {
         type = with types; nullOr (listOf int);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A list of values to be added to the temperature of each sensor,
           can be used to equalize small discrepancies in temperature ratings.
         '';
@@ -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" ];
       };
@@ -118,7 +120,7 @@ in {
       smartSupport = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to build thinkfan with S.M.A.R.T. support to read temperatures
           directly from hard disks.
         '';
@@ -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 {
@@ -159,7 +165,7 @@ in {
           [7  60  85]
           ["level auto" 80 32767]
         ];
-        description = ''
+        description = lib.mdDoc ''
           [LEVEL LOW HIGH]
 
           LEVEL is the fan level to use: it can be an integer (0-7 with thinkpad_acpi),
@@ -175,7 +181,7 @@ in {
         type = types.listOf types.str;
         default = [ ];
         example = [ "-b" "0" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of extra command line arguments to pass to thinkfan.
           Check the thinkfan(1) manpage for available arguments.
         '';
@@ -184,12 +190,12 @@ in {
       settings = mkOption {
         type = types.attrsOf settingsFormat.type;
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           Thinkfan settings. Use this option to configure thinkfan
           settings not exposed in a NixOS option or to bypass one.
-          Before changing this, read the <literal>thinkfan.conf(5)</literal>
+          Before changing this, read the `thinkfan.conf(5)`
           manpage and take a look at the example config file at
-          <link xlink:href="https://github.com/vmatare/thinkfan/blob/master/examples/thinkfan.yaml"/>
+          <https://github.com/vmatare/thinkfan/blob/master/examples/thinkfan.yaml>
         '';
       };
 
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
index 1905eb565c6d..99735ff6519d 100644
--- a/nixos/modules/services/hardware/throttled.nix
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -7,12 +7,12 @@ 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;
         default = "";
-        description = "Alternative configuration";
+        description = lib.mdDoc "Alternative configuration";
       };
     };
   };
diff --git a/nixos/modules/services/hardware/tlp.nix b/nixos/modules/services/hardware/tlp.nix
index eb53f565a67f..d2cc7c661c69 100644
--- a/nixos/modules/services/hardware/tlp.nix
+++ b/nixos/modules/services/hardware/tlp.nix
@@ -20,7 +20,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the TLP power management daemon.";
+        description = lib.mdDoc "Whether to enable the TLP power management daemon.";
       };
 
       settings = mkOption {type = with types; attrsOf (oneOf [bool int float str (listOf str)]);
@@ -29,7 +29,7 @@ in
           SATA_LINKPWR_ON_BAT = "med_power_with_dipm";
           USB_BLACKLIST_PHONE = 1;
         };
-        description = ''
+        description = lib.mdDoc ''
           Options passed to TLP. See https://linrunner.de/tlp for all supported options..
         '';
       };
@@ -37,7 +37,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Verbatim additional configuration variables for TLP.
           DEPRECATED: use services.tlp.settings instead.
         '';
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index a65d4250c2e5..70c1fd09860e 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -18,7 +18,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Trezor bridge daemon, for use with Trezor hardware bitcoin wallets.
         '';
       };
@@ -26,7 +26,7 @@ in {
       emulator.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Trezor emulator support.
           '';
        };
@@ -34,7 +34,7 @@ in {
       emulator.port = mkOption {
         type = types.port;
         default = 21324;
-        description = ''
+        description = lib.mdDoc ''
           Listening port for the Trezor emulator.
           '';
       };
diff --git a/nixos/modules/services/hardware/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix
index c2fa87875e11..54eac70643ff 100644
--- a/nixos/modules/services/hardware/triggerhappy.nix
+++ b/nixos/modules/services/hardware/triggerhappy.nix
@@ -22,18 +22,18 @@ let
 
       keys = mkOption {
         type = types.listOf types.str;
-        description = "List of keys to match.  Key names as defined in linux/input-event-codes.h";
+        description = lib.mdDoc "List of keys to match.  Key names as defined in linux/input-event-codes.h";
       };
 
       event = mkOption {
         type = types.enum ["press" "hold" "release"];
         default = "press";
-        description = "Event to match.";
+        description = lib.mdDoc "Event to match.";
       };
 
       cmd = mkOption {
         type = types.str;
-        description = "What to run.";
+        description = lib.mdDoc "What to run.";
       };
 
     };
@@ -52,8 +52,8 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to enable the <command>triggerhappy</command> hotkey daemon.
+        description = lib.mdDoc ''
+          Whether to enable the {command}`triggerhappy` hotkey daemon.
         '';
       };
 
@@ -61,8 +61,8 @@ in
         type = types.str;
         default = "nobody";
         example = "root";
-        description = ''
-          User account under which <command>triggerhappy</command> runs.
+        description = lib.mdDoc ''
+          User account under which {command}`triggerhappy` runs.
         '';
       };
 
@@ -72,16 +72,16 @@ in
         example = lib.literalExpression ''
           [ { keys = ["PLAYPAUSE"];  cmd = "''${pkgs.mpc-cli}/bin/mpc -q toggle"; } ]
         '';
-        description = ''
-          Key bindings for <command>triggerhappy</command>.
+        description = lib.mdDoc ''
+          Key bindings for {command}`triggerhappy`.
         '';
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Literal contents to append to the end of <command>triggerhappy</command> configuration file.
+        description = lib.mdDoc ''
+          Literal contents to append to the end of {command}`triggerhappy` configuration file.
         '';
       };
 
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 8257eeb673b9..d95261332419 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -8,6 +8,24 @@ let
 
   cfg = config.services.udev;
 
+  initrdUdevRules = pkgs.runCommand "initrd-udev-rules" {} ''
+    mkdir -p $out/etc/udev/rules.d
+    for f in 60-cdrom_id 60-persistent-storage 75-net-description 80-drivers 80-net-setup-link; do
+      ln -s ${config.boot.initrd.systemd.package}/lib/udev/rules.d/$f.rules $out/etc/udev/rules.d
+    done
+  '';
+
+
+  # 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;
@@ -28,6 +46,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;
@@ -153,6 +176,11 @@ let
       mv etc/udev/hwdb.bin $out
     '';
 
+  compressFirmware = firmware: if (config.boot.kernelPackages.kernelAtLeast "5.3" && (firmware.compressFirmware or true)) then
+    pkgs.compressFirmwareXz firmware
+  else
+    id firmware;
+
   # Udev has a 512-character limit for ENV{PATH}, so create a symlink
   # tree to work around this.
   udevPath = pkgs.buildEnv {
@@ -169,11 +197,10 @@ in
   ###### interface
 
   options = {
-
     boot.hardwareScan = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to try to load kernel modules for all detected hardware.
         Usually this does a good job of providing you with the modules
         you need, but sometimes it can crash the system or cause other
@@ -182,15 +209,18 @@ in
     };
 
     services.udev = {
+      enable = mkEnableOption (lib.mdDoc "udev") // {
+        default = true;
+      };
 
       packages = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
-          List of packages containing <command>udev</command> rules.
+        description = lib.mdDoc ''
+          List of packages containing {command}`udev` rules.
           All files found in
-          <filename><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
-          <filename><replaceable>pkg</replaceable>/lib/udev/rules.d</filename>
+          {file}`«pkg»/etc/udev/rules.d` and
+          {file}`«pkg»/lib/udev/rules.d`
           will be included.
         '';
         apply = map getBin;
@@ -199,8 +229,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.
         '';
       };
@@ -211,9 +241,9 @@ in
           ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="0825", ENV{PULSE_IGNORE}="1"
         '';
         type = types.lines;
-        description = ''
-          Additional <command>udev</command> rules. They'll be written
-          into file <filename>99-local.rules</filename>. Thus they are
+        description = lib.mdDoc ''
+          Additional {command}`udev` rules. They'll be written
+          into file {file}`99-local.rules`. Thus they are
           read and applied after all other rules.
         '';
       };
@@ -226,9 +256,9 @@ in
             KEYBOARD_KEY_700e2=leftctrl
         '';
         type = types.lines;
-        description = ''
-          Additional <command>hwdb</command> files. They'll be written
-          into file <filename>99-local.hwdb</filename>. Thus they are
+        description = lib.mdDoc ''
+          Additional {command}`hwdb` files. They'll be written
+          into file {file}`99-local.hwdb`. Thus they are
           read after all other files.
         '';
       };
@@ -238,7 +268,7 @@ in
     hardware.firmware = mkOption {
       type = types.listOf types.package;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         List of packages containing firmware files.  Such files
         will be loaded automatically if the kernel asks for them
         (i.e., when it has detected specific hardware that requires
@@ -249,7 +279,7 @@ in
       '';
       apply = list: pkgs.buildEnv {
         name = "firmware";
-        paths = list;
+        paths = map compressFirmware list;
         pathsToLink = [ "/lib/firmware" ];
         ignoreCollisions = true;
       };
@@ -258,16 +288,15 @@ in
     networking.usePredictableInterfaceNames = mkOption {
       default = true;
       type = types.bool;
-      description = ''
-        Whether to assign <link
-        xlink:href='http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames'>predictable
-        names to network interfaces</link>.  If enabled, interfaces
+      description = lib.mdDoc ''
+        Whether to assign [predictable names to network interfaces](http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames).
+        If enabled, interfaces
         are assigned names that contain topology information
-        (e.g. <literal>wlp3s0</literal>) and thus should be stable
+        (e.g. `wlp3s0`) and thus should be stable
         across reboots.  If disabled, names depend on the order in
         which interfaces are discovered by the kernel, which may
         change randomly across reboots; for instance, you may find
-        <literal>eth0</literal> and <literal>eth1</literal> flipping
+        `eth0` and `eth1` flipping
         unpredictably.
       '';
     };
@@ -278,13 +307,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><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
-          <filename><replaceable>pkg</replaceable>/lib/udev/rules.d</filename>
+          {file}`«pkg»/etc/udev/rules.d` and
+          {file}`«pkg»/lib/udev/rules.d`
           will be included.
         '';
       };
@@ -293,8 +322,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.
@@ -308,10 +337,10 @@ in
           SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card"
         '';
         type = types.lines;
-        description = ''
-          <command>udev</command> rules to include in the initrd
-          <emphasis>only</emphasis>. They'll be written into file
-          <filename>99-local.rules</filename>. Thus they are read and applied
+        description = lib.mdDoc ''
+          {command}`udev` rules to include in the initrd
+          *only*. They'll be written into file
+          {file}`99-local.rules`. Thus they are read and applied
           after the essential initrd rules.
         '';
       };
@@ -323,7 +352,7 @@ in
 
   ###### implementation
 
-  config = mkIf (!config.boot.isContainer) {
+  config = mkIf cfg.enable {
 
     services.udev.extraRules = nixosRules;
 
@@ -340,8 +369,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"
@@ -350,7 +381,10 @@ in
     ];
     boot.initrd.systemd.storePaths = [
       "${config.boot.initrd.systemd.package}/lib/systemd/systemd-udevd"
-      "${config.boot.initrd.systemd.package}/lib/udev"
+      "${config.boot.initrd.systemd.package}/lib/udev/ata_id"
+      "${config.boot.initrd.systemd.package}/lib/udev/cdrom_id"
+      "${config.boot.initrd.systemd.package}/lib/udev/scsi_id"
+      "${config.boot.initrd.systemd.package}/lib/udev/rules.d"
     ] ++ map (x: "${x}/bin") config.boot.initrd.services.udev.binPackages;
 
     # Generate the udev rules for the initrd
@@ -364,13 +398,17 @@ 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 custom rules
-    boot.initrd.services.udev.packages = mkIf (config.boot.initrd.services.udev.rules != "") (pkgs.writeTextFile {
-      name = "initrd-udev-rules";
-      destination = "/etc/udev/rules.d/99-local.rules";
-      text = config.boot.initrd.services.udev.rules;
-    });
+    # Insert initrd rules
+    boot.initrd.services.udev.packages = [
+      initrdUdevRules
+      (mkIf (config.boot.initrd.services.udev.rules != "") (pkgs.writeTextFile {
+        name = "initrd-udev-rules";
+        destination = "/etc/udev/rules.d/99-local.rules";
+        text = config.boot.initrd.services.udev.rules;
+      }))
+    ];
 
     environment.etc =
       {
diff --git a/nixos/modules/services/hardware/udisks2.nix b/nixos/modules/services/hardware/udisks2.nix
index ea552ce867e8..7368845dafd5 100644
--- a/nixos/modules/services/hardware/udisks2.nix
+++ b/nixos/modules/services/hardware/udisks2.nix
@@ -19,14 +19,7 @@ in
 
     services.udisks2 = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to enable Udisks, a DBus service that allows
-          applications to query and manipulate storage devices.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "udisks2, a DBus service that allows applications to query and manipulate storage devices.");
 
       settings = mkOption rec {
         type = types.attrsOf settingsFormat.type;
@@ -51,10 +44,10 @@ in
           };
         };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Options passed to udisksd.
-          See <link xlink:href="http://manpages.ubuntu.com/manpages/latest/en/man5/udisks2.conf.5.html">here</link> and
-          drive configuration in <link xlink:href="http://manpages.ubuntu.com/manpages/latest/en/man8/udisks.8.html">here</link> for supported options.
+          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.
         '';
       };
 
@@ -69,7 +62,12 @@ 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;
 
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index a743bbf21c8c..c49d944cdc18 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -33,16 +33,16 @@ 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;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable verbose logging.
       '';
     };
@@ -51,7 +51,7 @@ in
       type = types.package;
       default = pkgs.undervolt;
       defaultText = literalExpression "pkgs.undervolt";
-      description = ''
+      description = lib.mdDoc ''
         undervolt derivation to use.
       '';
     };
@@ -59,7 +59,7 @@ in
     coreOffset = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The amount of voltage in mV to offset the CPU cores by.
       '';
     };
@@ -67,7 +67,7 @@ in
     gpuOffset = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The amount of voltage in mV to offset the GPU by.
       '';
     };
@@ -75,7 +75,7 @@ in
     uncoreOffset = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The amount of voltage in mV to offset uncore by.
       '';
     };
@@ -83,7 +83,7 @@ in
     analogioOffset = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The amount of voltage in mV to offset analogio by.
       '';
     };
@@ -91,7 +91,7 @@ in
     temp = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The temperature target in Celsius degrees.
       '';
     };
@@ -99,7 +99,7 @@ in
     tempAc = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The temperature target on AC power in Celsius degrees.
       '';
     };
@@ -107,7 +107,7 @@ in
     tempBat = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The temperature target on battery power in Celsius degrees.
       '';
     };
@@ -115,7 +115,7 @@ in
     p1.limit = mkOption {
       type = with types; nullOr int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The P1 Power Limit in Watts.
         Both limit and window must be set.
       '';
@@ -123,7 +123,7 @@ in
     p1.window = mkOption {
       type = with types; nullOr (oneOf [ float int ]);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The P1 Time Window in seconds.
         Both limit and window must be set.
       '';
@@ -132,7 +132,7 @@ in
     p2.limit = mkOption {
       type = with types; nullOr int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The P2 Power Limit in Watts.
         Both limit and window must be set.
       '';
@@ -140,7 +140,7 @@ in
     p2.window = mkOption {
       type = with types; nullOr (oneOf [ float int ]);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The P2 Time Window in seconds.
         Both limit and window must be set.
       '';
@@ -149,7 +149,7 @@ in
     useTimer = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to set a timer that applies the undervolt settings every 30s.
         This will cause spam in the journal but might be required for some
         hardware under specific conditions.
diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix
index 81bf497c993d..aacc8a63dbeb 100644
--- a/nixos/modules/services/hardware/upower.nix
+++ b/nixos/modules/services/hardware/upower.nix
@@ -21,7 +21,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Upower, a DBus service that provides power
           management support to applications.
         '';
@@ -31,7 +31,7 @@ in
         type = types.package;
         default = pkgs.upower;
         defaultText = literalExpression "pkgs.upower";
-        description = ''
+        description = lib.mdDoc ''
           Which upower package to use.
         '';
       };
@@ -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,17 +49,15 @@ 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
         '';
       };
 
       noPollBatteries = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Don't poll the kernel for battery level changes.
 
           Some hardware will send us battery level changes through
@@ -71,7 +69,7 @@ in
       ignoreLid = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Do we ignore the lid state
 
           Some laptops are broken. The lid state is either inverted, or stuck
@@ -85,7 +83,7 @@ in
       usePercentageForPolicy = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Policy for warnings and action based on battery levels
 
           Whether battery percentage based policy should be used. The default
@@ -99,17 +97,17 @@ in
       percentageLow = mkOption {
         type = types.ints.unsigned;
         default = 10;
-        description = ''
-          When <literal>usePercentageForPolicy</literal> is
-          <literal>true</literal>, the levels at which UPower will consider the
+        description = lib.mdDoc ''
+          When `usePercentageForPolicy` is
+          `true`, the levels at which UPower will consider the
           battery low.
 
           This will also be used for batteries which don't have time information
           such as that of peripherals.
 
-          If any value (of <literal>percentageLow</literal>,
-          <literal>percentageCritical</literal> and
-          <literal>percentageAction</literal>) is invalid, or not in descending
+          If any value (of `percentageLow`,
+          `percentageCritical` and
+          `percentageAction`) is invalid, or not in descending
           order, the defaults will be used.
         '';
       };
@@ -117,17 +115,17 @@ in
       percentageCritical = mkOption {
         type = types.ints.unsigned;
         default = 3;
-        description = ''
-          When <literal>usePercentageForPolicy</literal> is
-          <literal>true</literal>, the levels at which UPower will consider the
+        description = lib.mdDoc ''
+          When `usePercentageForPolicy` is
+          `true`, the levels at which UPower will consider the
           battery critical.
 
           This will also be used for batteries which don't have time information
           such as that of peripherals.
 
-          If any value (of <literal>percentageLow</literal>,
-          <literal>percentageCritical</literal> and
-          <literal>percentageAction</literal>) is invalid, or not in descending
+          If any value (of `percentageLow`,
+          `percentageCritical` and
+          `percentageAction`) is invalid, or not in descending
           order, the defaults will be used.
         '';
       };
@@ -135,17 +133,17 @@ in
       percentageAction = mkOption {
         type = types.ints.unsigned;
         default = 2;
-        description = ''
-          When <literal>usePercentageForPolicy</literal> is
-          <literal>true</literal>, the levels at which UPower will take action
+        description = lib.mdDoc ''
+          When `usePercentageForPolicy` is
+          `true`, the levels at which UPower will take action
           for the critical battery level.
 
           This will also be used for batteries which don't have time information
           such as that of peripherals.
 
-          If any value (of <literal>percentageLow</literal>,
-          <literal>percentageCritical</literal> and
-          <literal>percentageAction</literal>) is invalid, or not in descending
+          If any value (of `percentageLow`,
+          `percentageCritical` and
+          `percentageAction`) is invalid, or not in descending
           order, the defaults will be used.
         '';
       };
@@ -153,13 +151,13 @@ in
       timeLow = mkOption {
         type = types.ints.unsigned;
         default = 1200;
-        description = ''
-          When <literal>usePercentageForPolicy</literal> is
-          <literal>false</literal>, the time remaining in seconds at which
+        description = lib.mdDoc ''
+          When `usePercentageForPolicy` is
+          `false`, the time remaining in seconds at which
           UPower will consider the battery low.
 
-          If any value (of <literal>timeLow</literal>,
-          <literal>timeCritical</literal> and <literal>timeAction</literal>) is
+          If any value (of `timeLow`,
+          `timeCritical` and `timeAction`) is
           invalid, or not in descending order, the defaults will be used.
         '';
       };
@@ -167,13 +165,13 @@ in
       timeCritical = mkOption {
         type = types.ints.unsigned;
         default = 300;
-        description = ''
-          When <literal>usePercentageForPolicy</literal> is
-          <literal>false</literal>, the time remaining in seconds at which
+        description = lib.mdDoc ''
+          When `usePercentageForPolicy` is
+          `false`, the time remaining in seconds at which
           UPower will consider the battery critical.
 
-          If any value (of <literal>timeLow</literal>,
-          <literal>timeCritical</literal> and <literal>timeAction</literal>) is
+          If any value (of `timeLow`,
+          `timeCritical` and `timeAction`) is
           invalid, or not in descending order, the defaults will be used.
         '';
       };
@@ -181,13 +179,13 @@ in
       timeAction = mkOption {
         type = types.ints.unsigned;
         default = 120;
-        description = ''
-          When <literal>usePercentageForPolicy</literal> is
-          <literal>false</literal>, the time remaining in seconds at which
+        description = lib.mdDoc ''
+          When `usePercentageForPolicy` is
+          `false`, the time remaining in seconds at which
           UPower will take action for the critical battery level.
 
-          If any value (of <literal>timeLow</literal>,
-          <literal>timeCritical</literal> and <literal>timeAction</literal>) is
+          If any value (of `timeLow`,
+          `timeCritical` and `timeAction`) is
           invalid, or not in descending order, the defaults will be used.
         '';
       };
@@ -195,9 +193,9 @@ in
       criticalPowerAction = mkOption {
         type = types.enum [ "PowerOff" "Hibernate" "HybridSleep" ];
         default = "HybridSleep";
-        description = ''
-          The action to take when <literal>timeAction</literal> or
-          <literal>percentageAction</literal> has been reached for the batteries
+        description = lib.mdDoc ''
+          The action to take when `timeAction` or
+          `percentageAction` has been reached for the batteries
           (UPS or laptop batteries) supplying the computer
         '';
       };
diff --git a/nixos/modules/services/hardware/usbmuxd.nix b/nixos/modules/services/hardware/usbmuxd.nix
index 11a4b0a858f9..9466ea26995b 100644
--- a/nixos/modules/services/hardware/usbmuxd.nix
+++ b/nixos/modules/services/hardware/usbmuxd.nix
@@ -13,10 +13,11 @@ in
 
 {
   options.services.usbmuxd = {
+
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable the usbmuxd ("USB multiplexing daemon") service. This daemon is
         in charge of multiplexing connections over USB to an iOS device. This is
         needed for transferring data from and to iOS devices (see ifuse). Also
@@ -27,7 +28,7 @@ in
     user = mkOption {
       type = types.str;
       default = defaultUserGroup;
-      description = ''
+      description = lib.mdDoc ''
         The user usbmuxd should use to run after startup.
       '';
     };
@@ -35,10 +36,19 @@ in
     group = mkOption {
       type = types.str;
       default = defaultUserGroup;
-      description = ''
+      description = lib.mdDoc ''
         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/nixos/modules/services/hardware/usbrelayd.nix b/nixos/modules/services/hardware/usbrelayd.nix
index c0322e89e6b1..01d3a5ba8bee 100644
--- a/nixos/modules/services/hardware/usbrelayd.nix
+++ b/nixos/modules/services/hardware/usbrelayd.nix
@@ -5,11 +5,11 @@ 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;
-      description = "Hostname or IP address of your MQTT Broker.";
+      description = lib.mdDoc "Hostname or IP address of your MQTT Broker.";
       default = "127.0.0.1";
       example = [
         "mqtt"
@@ -19,15 +19,14 @@ in
 
     clientName = mkOption {
       type = str;
-      description = "Name, your client connects as.";
+      description = lib.mdDoc "Name, your client connects as.";
       default = "MyUSBRelay";
     };
   };
 
   config = mkIf cfg.enable {
 
-    # TODO: Rename to .conf in upcomming release
-    environment.etc."usbrelayd.ini".text = ''
+    environment.etc."usbrelayd.conf".text = ''
       [MQTT]
       BROKER = ${cfg.broker}
       CLIENTNAME = ${cfg.clientName}
@@ -35,10 +34,10 @@ in
 
     services.udev.packages = [ pkgs.usbrelayd ];
     systemd.packages = [ pkgs.usbrelayd ];
-    users.users.usbrelay = {
-      isSystemUser = true;
-      group = "usbrelay";
-    };
     users.groups.usbrelay = { };
   };
+
+  meta = {
+    maintainers = with lib.maintainers; [ wentasah ];
+  };
 }
diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix
index 5ec222b805c8..de63ed893b02 100644
--- a/nixos/modules/services/hardware/vdr.nix
+++ b/nixos/modules/services/hardware/vdr.nix
@@ -12,29 +12,29 @@ 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;
         default = pkgs.vdr;
         defaultText = literalExpression "pkgs.vdr";
         example = literalExpression "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
-        description = "Package to use.";
+        description = lib.mdDoc "Package to use.";
       };
 
       videoDir = mkOption {
         type = types.path;
         default = "/srv/vdr/video";
-        description = "Recording directory";
+        description = lib.mdDoc "Recording directory";
       };
 
       extraArguments = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Additional command line arguments to pass to VDR.";
+        description = lib.mdDoc "Additional command line arguments to pass to VDR.";
       };
 
-      enableLirc = mkEnableOption "LIRC";
+      enableLirc = mkEnableOption (lib.mdDoc "LIRC");
     };
   };
 
diff --git a/nixos/modules/services/hardware/xow.nix b/nixos/modules/services/hardware/xow.nix
deleted file mode 100644
index 311181176bd8..000000000000
--- a/nixos/modules/services/hardware/xow.nix
+++ /dev/null
@@ -1,20 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  cfg = config.services.hardware.xow;
-in {
-  options.services.hardware.xow = {
-    enable = lib.mkEnableOption "xow as a systemd service";
-  };
-
-  config = lib.mkIf cfg.enable {
-    hardware.uinput.enable = true;
-
-    boot.extraModprobeConfig = lib.readFile "${pkgs.xow}/lib/modprobe.d/xow-blacklist.conf";
-
-    systemd.packages = [ pkgs.xow ];
-    systemd.services.xow.wantedBy = [ "multi-user.target" ];
-
-    services.udev.packages = [ pkgs.xow ];
-  };
-}
diff --git a/nixos/modules/services/home-automation/evcc.nix b/nixos/modules/services/home-automation/evcc.nix
new file mode 100644
index 000000000000..efa2cf244313
--- /dev/null
+++ b/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/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index 6022227f6ea8..fa06e5391bbf 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -77,12 +77,12 @@ 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";
       type = types.path;
-      description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
+      description = lib.mdDoc "The config directory, where your {file}`configuration.yaml` is located.";
     };
 
     extraComponents = mkOption {
@@ -92,7 +92,7 @@ in {
         "default_config"
         "met"
         "esphome"
-      ] ++ optionals (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) [
+      ] ++ optionals pkgs.stdenv.hostPlatform.isAarch [
         # Use the platform as an indicator that we might be running on a RaspberryPi and include
         # relevant components
         "rpi_power"
@@ -107,10 +107,10 @@ in {
           "wled"
         ]
       '';
-      description = ''
-        List of <link xlink:href="https://www.home-assistant.io/integrations/">components</link> that have their dependencies included in the package.
+      description = lib.mdDoc ''
+        List of [components](https://www.home-assistant.io/integrations/) that have their dependencies included in the package.
 
-        The component name can be found in the URL, for example <literal>https://www.home-assistant.io/integrations/ffmpeg/</literal> would map to <literal>ffmpeg</literal>.
+        The component name can be found in the URL, for example `https://www.home-assistant.io/integrations/ffmpeg/` would map to `ffmpeg`.
       '';
     };
 
@@ -126,10 +126,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.
       '';
     };
@@ -148,7 +148,7 @@ in {
               type = types.nullOr types.str;
               default = null;
               example = "Home";
-              description = ''
+              description = lib.mdDoc ''
                 Name of the location where Home Assistant is running.
               '';
             };
@@ -157,7 +157,7 @@ in {
               type = types.nullOr (types.either types.float types.str);
               default = null;
               example = 52.3;
-              description = ''
+              description = lib.mdDoc ''
                 Latitude of your location required to calculate the time the sun rises and sets.
               '';
             };
@@ -166,7 +166,7 @@ in {
               type = types.nullOr (types.either types.float types.str);
               default = null;
               example = 4.9;
-              description = ''
+              description = lib.mdDoc ''
                 Longitude of your location required to calculate the time the sun rises and sets.
               '';
             };
@@ -175,7 +175,7 @@ in {
               type = types.nullOr (types.enum [ "metric" "imperial" ]);
               default = null;
               example = "metric";
-              description = ''
+              description = lib.mdDoc ''
                 The unit system to use. This also sets temperature_unit, Celsius for Metric and Fahrenheit for Imperial.
               '';
             };
@@ -184,8 +184,8 @@ in {
               type = types.nullOr (types.enum [ "C" "F" ]);
               default = null;
               example = "C";
-              description = ''
-                Override temperature unit set by unit_system. <literal>C</literal> for Celsius, <literal>F</literal> for Fahrenheit.
+              description = lib.mdDoc ''
+                Override temperature unit set by unit_system. `C` for Celsius, `F` for Fahrenheit.
               '';
             };
 
@@ -196,8 +196,8 @@ in {
                 config.time.timeZone or null
               '';
               example = "Europe/Amsterdam";
-              description = ''
-                Pick your time zone from the column TZ of Wikipedia’s <link xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">list of tz database time zones</link>.
+              description = lib.mdDoc ''
+                Pick your time zone from the column TZ of Wikipedia’s [list of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
               '';
             };
           };
@@ -211,7 +211,7 @@ in {
                 "::"
               ];
               example = "::1";
-              description = ''
+              description = lib.mdDoc ''
                 Only listen to incoming requests on specific IP/host. The default listed assumes support for IPv4 and IPv6.
               '';
             };
@@ -219,7 +219,7 @@ in {
             server_port = mkOption {
               default = 8123;
               type = types.port;
-              description = ''
+              description = lib.mdDoc ''
                 The port on which to listen.
               '';
             };
@@ -238,8 +238,8 @@ in {
                 else "storage";
               '';
               example = "yaml";
-              description = ''
-                In what mode should the main Lovelace panel be, <literal>yaml</literal> or <literal>storage</literal> (UI managed).
+              description = lib.mdDoc ''
+                In what mode should the main Lovelace panel be, `yaml` or `storage` (UI managed).
               '';
             };
           };
@@ -262,14 +262,14 @@ in {
           feedreader.urls = [ "https://nixos.org/blogs.xml" ];
         }
       '';
-      description = ''
-        Your <filename>configuration.yaml</filename> as a Nix attribute set.
+      description = lib.mdDoc ''
+        Your {file}`configuration.yaml` as a Nix attribute set.
 
-        YAML functions like <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">secrets</link>
+        YAML functions like [secrets](https://www.home-assistant.io/docs/configuration/secrets/)
         can be passed as a string and will be unquoted automatically.
 
-        Unless this option is explicitly set to <literal>null</literal>
-        we assume your <filename>configuration.yaml</filename> is
+        Unless this option is explicitly set to `null`
+        we assume your {file}`configuration.yaml` is
         managed through this module and thereby overwritten on startup.
       '';
     };
@@ -277,12 +277,12 @@ in {
     configWritable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
-        Whether to make <filename>configuration.yaml</filename> writable.
+      description = lib.mdDoc ''
+        Whether to make {file}`configuration.yaml` writable.
 
         This will allow you to edit it from Home Assistant's web interface.
 
-        This only has an effect if <option>config</option> is set.
+        This only has an effect if {option}`config` is set.
         However, bear in mind that it will be overwritten at every start of the service.
       '';
     };
@@ -304,23 +304,23 @@ in {
           } ];
         }
       '';
-      description = ''
-        Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set.
-        Setting this option will automatically set <literal>lovelace.mode</literal> to <literal>yaml</literal>.
+      description = lib.mdDoc ''
+        Your {file}`ui-lovelace.yaml` as a Nix attribute set.
+        Setting this option will automatically set `lovelace.mode` to `yaml`.
 
-        Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
+        Beware that setting this option will delete your previous {file}`ui-lovelace.yaml`
       '';
     };
 
     lovelaceConfigWritable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
-        Whether to make <filename>ui-lovelace.yaml</filename> writable.
+      description = lib.mdDoc ''
+        Whether to make {file}`ui-lovelace.yaml` writable.
 
         This will allow you to edit it from Home Assistant's web interface.
 
-        This only has an effect if <option>lovelaceConfig</option> is set.
+        This only has an effect if {option}`lovelaceConfig` is set.
         However, bear in mind that it will be overwritten at every start of the service.
       '';
     };
@@ -347,7 +347,7 @@ in {
           ];
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         The Home Assistant package to use.
       '';
     };
@@ -355,12 +355,30 @@ in {
     openFirewall = mkOption {
       default = false;
       type = types.bool;
-      description = "Whether to open the firewall for the specified port.";
+      description = lib.mdDoc "Whether to open the firewall for the specified port.";
     };
   };
 
   config = mkIf cfg.enable {
-    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+    assertions = [
+      {
+        assertion = cfg.openFirewall -> !isNull cfg.config;
+        message = "openFirewall can only be used with a declarative config";
+      }
+    ];
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.config.http.server_port ];
+
+    # symlink the configuration to /etc/home-assistant
+    environment.etc = lib.mkMerge [
+      (lib.mkIf (cfg.config != null && !cfg.configWritable) {
+        "home-assistant/configuration.yaml".source = configFile;
+      })
+
+      (lib.mkIf (cfg.lovelaceConfig != null && !cfg.lovelaceConfigWritable) {
+        "home-assistant/ui-lovelace.yaml".source = lovelaceConfigFile;
+      })
+    ];
 
     systemd.services.home-assistant = {
       description = "Home Assistant";
@@ -371,18 +389,21 @@ in {
         "mysql.service"
         "postgresql.service"
       ];
+      reloadTriggers = lib.optional (cfg.config != null) configFile
+      ++ lib.optional (cfg.lovelaceConfig != null) lovelaceConfigFile;
+
       preStart = let
         copyConfig = if cfg.configWritable then ''
           cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
         '' else ''
           rm -f "${cfg.configDir}/configuration.yaml"
-          ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
+          ln -s /etc/home-assistant/configuration.yaml "${cfg.configDir}/configuration.yaml"
         '';
         copyLovelaceConfig = if cfg.lovelaceConfigWritable then ''
           cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
         '' else ''
           rm -f "${cfg.configDir}/ui-lovelace.yaml"
-          ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+          ln -s /etc/home-assistant/ui-lovelace.yaml "${cfg.configDir}/ui-lovelace.yaml"
         '';
       in
         (optionalString (cfg.config != null) copyConfig) +
@@ -390,12 +411,12 @@ in {
       ;
       serviceConfig = let
         # List of capabilities to equip home-assistant with, depending on configured components
-        capabilities = [
+        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
           ""
-        ] ++ (unique (optionals (useComponent "bluetooth_tracker" || useComponent "bluetooth_le_tracker") [
-          # Required for interaction with hci devices and bluetooth sockets
+        ] ++ 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"
           "CAP_NET_RAW"
@@ -408,11 +429,43 @@ in {
           "CAP_NET_ADMIN"
           "CAP_NET_BIND_SERVICE"
           "CAP_NET_RAW"
-        ]));
+        ]);
         componentsUsingBluetooth = [
           # Components that require the AF_BLUETOOTH address family
-          "bluetooth_tracker"
+          "august"
+          "august_ble"
+          "airthings_ble"
+          "aranet"
+          "bluemaestro"
+          "bluetooth"
           "bluetooth_le_tracker"
+          "bluetooth_tracker"
+          "bthome"
+          "default_config"
+          "eq3btsmart"
+          "esphome"
+          "fjaraskupan"
+          "govee_ble"
+          "homekit_controller"
+          "inkbird"
+          "keymitt_ble"
+          "led_ble"
+          "melnor"
+          "moat"
+          "oralb"
+          "qingping"
+          "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
@@ -429,7 +482,6 @@ in {
           # mostly the ones using config flows already.
           "acer_projector"
           "alarmdecoder"
-          "arduino"
           "blackbird"
           "deconz"
           "dsmr"
@@ -443,7 +495,6 @@ in {
           "insteon"
           "kwb"
           "lacrosse"
-          "mhz19"
           "modbus"
           "modem_callerid"
           "mysensors"
@@ -459,7 +510,6 @@ in {
           "usb"
           "velbus"
           "w800rf32"
-          "xbee"
           "zha"
           "zwave"
           "zwave_js"
diff --git a/nixos/modules/services/home-automation/zigbee2mqtt.nix b/nixos/modules/services/home-automation/zigbee2mqtt.nix
index ff6d595e5a6e..796de3a491e4 100644
--- a/nixos/modules/services/home-automation/zigbee2mqtt.nix
+++ b/nixos/modules/services/home-automation/zigbee2mqtt.nix
@@ -18,10 +18,10 @@ in
   ];
 
   options.services.zigbee2mqtt = {
-    enable = mkEnableOption "enable zigbee2mqtt service";
+    enable = mkEnableOption (lib.mdDoc "zigbee2mqtt service");
 
     package = mkOption {
-      description = "Zigbee2mqtt package to use";
+      description = lib.mdDoc "Zigbee2mqtt package to use";
       default = pkgs.zigbee2mqtt;
       defaultText = literalExpression ''
         pkgs.zigbee2mqtt
@@ -30,7 +30,7 @@ in
     };
 
     dataDir = mkOption {
-      description = "Zigbee2mqtt data directory";
+      description = lib.mdDoc "Zigbee2mqtt data directory";
       default = "/var/lib/zigbee2mqtt";
       type = types.path;
     };
@@ -47,9 +47,9 @@ in
           };
         }
       '';
-      description = ''
-        Your <filename>configuration.yaml</filename> as a Nix attribute set.
-        Check the <link xlink:href="https://www.zigbee2mqtt.io/information/configuration.html">documentation</link>
+      description = lib.mdDoc ''
+        Your {file}`configuration.yaml` as a Nix attribute set.
+        Check the [documentation](https://www.zigbee2mqtt.io/information/configuration.html)
         for possible options.
       '';
     };
@@ -119,9 +119,8 @@ in
         ];
         SystemCallArchitectures = "native";
         SystemCallFilter = [
-          "@system-service"
-          "~@privileged"
-          "~@resources"
+          "@system-service @pkey"
+          "~@privileged @resources"
         ];
         UMask = "0077";
       };
diff --git a/nixos/modules/services/logging/SystemdJournal2Gelf.nix b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
index f28ecab8ac23..3d85c2b62c63 100644
--- a/nixos/modules/services/logging/SystemdJournal2Gelf.nix
+++ b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
@@ -10,7 +10,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable SystemdJournal2Gelf.
         '';
       };
@@ -18,7 +18,7 @@ in
       graylogServer = mkOption {
         type = types.str;
         example = "graylog2.example.com:11201";
-        description = ''
+        description = lib.mdDoc ''
           Host and port of your graylog2 input. This should be a GELF
           UDP input.
         '';
@@ -27,9 +27,9 @@ in
       extraOptions = mkOption {
         type = types.separatedString " ";
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Any extra flags to pass to SystemdJournal2Gelf. Note that
-          these are basically <literal>journalctl</literal> flags.
+          these are basically `journalctl` flags.
         '';
       };
 
@@ -37,7 +37,7 @@ in
         type = types.package;
         default = pkgs.systemd-journal2gelf;
         defaultText = literalExpression "pkgs.systemd-journal2gelf";
-        description = ''
+        description = lib.mdDoc ''
           SystemdJournal2Gelf package to use.
         '';
       };
diff --git a/nixos/modules/services/logging/awstats.nix b/nixos/modules/services/logging/awstats.nix
index df0124380ff0..708775bfcf03 100644
--- a/nixos/modules/services/logging/awstats.nix
+++ b/nixos/modules/services/logging/awstats.nix
@@ -11,40 +11,40 @@ let
         type = types.enum [ "mail" "web" ];
         default = "web";
         example = "mail";
-        description = ''
+        description = lib.mdDoc ''
           The type of log being collected.
         '';
       };
       domain = mkOption {
         type = types.str;
         default = name;
-        description = "The domain name to collect stats for.";
+        description = lib.mdDoc "The domain name to collect stats for.";
         example = "example.com";
       };
 
       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>
+          ```
         '';
       };
 
@@ -52,7 +52,7 @@ let
         type = types.listOf types.str;
         default = [];
         example = [ "www.example.org" ];
-        description = ''
+        description = lib.mdDoc ''
           List of aliases the site has.
         '';
       };
@@ -65,22 +65,22 @@ let
             "ValidHTTPCodes" = "404";
           }
         '';
-        description = "Extra configuration to be appended to awstats.\${name}.conf.";
+        description = lib.mdDoc "Extra configuration to be appended to awstats.\${name}.conf.";
       };
 
       webService = {
-        enable = mkEnableOption "awstats web service";
+        enable = mkEnableOption (lib.mdDoc "awstats web service");
 
         hostname = mkOption {
           type = types.str;
           default = config.domain;
-          description = "The hostname the web service appears under.";
+          description = lib.mdDoc "The hostname the web service appears under.";
         };
 
         urlPrefix = mkOption {
           type = types.str;
           default = "/awstats";
-          description = "The URL prefix under which the awstats pages appear.";
+          description = lib.mdDoc "The URL prefix under which the awstats pages appear.";
         };
       };
     };
@@ -95,12 +95,12 @@ in
   ];
 
   options.services.awstats = {
-    enable = mkEnableOption "awstats";
+    enable = mkEnableOption (lib.mdDoc "awstats");
 
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/awstats";
-      description = "The directory where awstats data will be stored.";
+      description = lib.mdDoc "The directory where awstats data will be stored.";
     };
 
     configs = mkOption {
@@ -114,18 +114,16 @@ in
           };
         }
       '';
-      description = "Attribute set of domains to collect stats for.";
+      description = lib.mdDoc "Attribute set of domains to collect stats for.";
     };
 
     updateAt = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "hourly";
-      description = ''
+      description = lib.mdDoc ''
         Specification of the time at which awstats will get updated.
-        (in the format described by <citerefentry>
-          <refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>)
+        (in the format described by {manpage}`systemd.time(7)`)
       '';
     };
   };
diff --git a/nixos/modules/services/logging/filebeat.nix b/nixos/modules/services/logging/filebeat.nix
index 223a993c505b..5b5e7fd5ae89 100644
--- a/nixos/modules/services/logging/filebeat.nix
+++ b/nixos/modules/services/logging/filebeat.nix
@@ -18,33 +18,33 @@ in
 
     services.filebeat = {
 
-      enable = mkEnableOption "filebeat";
+      enable = mkEnableOption (lib.mdDoc "filebeat");
 
       package = mkOption {
         type = types.package;
         default = pkgs.filebeat;
         defaultText = literalExpression "pkgs.filebeat";
         example = literalExpression "pkgs.filebeat7";
-        description = ''
+        description = lib.mdDoc ''
           The filebeat package to use.
         '';
       };
 
       inputs = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Inputs specify how Filebeat locates and processes input data.
 
-          This is like <literal>services.filebeat.settings.filebeat.inputs</literal>,
+          This is like `services.filebeat.settings.filebeat.inputs`,
           but structured as an attribute set. This has the benefit
           that multiple NixOS modules can contribute settings to a
           single filebeat input.
 
           An input type can be specified multiple times by choosing a
-          different <literal>&lt;name></literal> for each, but setting
-          <xref linkend="opt-services.filebeat.inputs._name_.type"/>
+          different `<name>` for each, but setting
+          [](#opt-services.filebeat.inputs._name_.type)
           to the same value.
 
-          See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html"/>.
+          See <https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html>.
         '';
         default = {};
         type = types.attrsOf (types.submodule ({ name, ... }: {
@@ -53,12 +53,12 @@ in
             type = mkOption {
               type = types.str;
               default = name;
-              description = ''
+              description = lib.mdDoc ''
                 The input type.
 
-                Look for the value after <literal>type:</literal> on
+                Look for the value after `type:` on
                 the individual input pages linked from
-                <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html"/>.
+                <https://www.elastic.co/guide/en/beats/filebeat/current/configuration-filebeat-options.html>.
               '';
             };
           };
@@ -77,24 +77,24 @@ in
       };
 
       modules = mkOption {
-        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.
 
-          This is like <literal>services.filebeat.settings.filebeat.modules</literal>,
+          This is like `services.filebeat.settings.filebeat.modules`,
           but structured as an attribute set. This has the benefit
           that multiple NixOS modules can contribute settings to a
           single filebeat module.
 
           A module can be specified multiple times by choosing a
-          different <literal>&lt;name></literal> for each, but setting
-          <xref linkend="opt-services.filebeat.modules._name_.module"/>
+          different `<name>` for each, but setting
+          [](#opt-services.filebeat.modules._name_.module)
           to the same value.
 
-          See <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html"/>.
+          See <https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html>.
         '';
         default = {};
         type = types.attrsOf (types.submodule ({ name, ... }: {
@@ -103,12 +103,12 @@ in
             module = mkOption {
               type = types.str;
               default = name;
-              description = ''
+              description = lib.mdDoc ''
                 The name of the module.
 
-                Look for the value after <literal>module:</literal> on
+                Look for the value after `module:` on
                 the individual input pages linked from
-                <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html"/>.
+                <https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html>.
               '';
             };
           };
@@ -139,7 +139,7 @@ in
               type = with types; listOf str;
               default = [ "127.0.0.1:9200" ];
               example = [ "myEShost:9200" ];
-              description = ''
+              description = lib.mdDoc ''
                 The list of Elasticsearch nodes to connect to.
 
                 The events are distributed to these nodes in round
@@ -147,10 +147,10 @@ in
                 event is automatically sent to another node. Each
                 Elasticsearch node can be defined as a URL or
                 IP:PORT. For example:
-                <literal>http://192.15.3.2</literal>,
-                <literal>https://es.found.io:9230</literal> or
-                <literal>192.24.3.2:9300</literal>. If no port is
-                specified, <literal>9200</literal> is used.
+                `http://192.15.3.2`,
+                `https://es.found.io:9230` or
+                `192.24.3.2:9300`. If no port is
+                specified, `9200` is used.
               '';
             };
 
@@ -159,28 +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>.
                 '';
               };
             };
@@ -200,20 +199,20 @@ in
           };
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Configuration for filebeat. See
-          <link xlink:href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-reference-yml.html"/>
+          <https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-reference-yml.html>
           for supported values.
 
           Options containing secret data should be set to an attribute
-          set containing the attribute <literal>_secret</literal> - a
+          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
-          <filename>filebeat.yml</filename> file, the
-          <literal>output.elasticsearch.password</literal>
+          {file}`filebeat.yml` file, the
+          `output.elasticsearch.password`
           key will be set to the contents of the
-          <filename>/var/keys/elasticsearch_password</filename> file.
+          {file}`/var/keys/elasticsearch_password` file.
         '';
       };
     };
diff --git a/nixos/modules/services/logging/fluentd.nix b/nixos/modules/services/logging/fluentd.nix
index dd19617a13ff..7764aafb2d1a 100644
--- a/nixos/modules/services/logging/fluentd.nix
+++ b/nixos/modules/services/logging/fluentd.nix
@@ -12,29 +12,25 @@ in {
   options = {
 
     services.fluentd = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable fluentd.";
-      };
+      enable = mkEnableOption (lib.mdDoc "fluentd");
 
       config = mkOption {
         type = types.lines;
         default = "";
-        description = "Fluentd config.";
+        description = lib.mdDoc "Fluentd config.";
       };
 
       package = mkOption {
         type = types.path;
         default = pkgs.fluentd;
         defaultText = literalExpression "pkgs.fluentd";
-        description = "The fluentd package to use.";
+        description = lib.mdDoc "The fluentd package to use.";
       };
 
       plugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of plugin paths to pass into fluentd. It will make plugins defined in ruby files
           there available in your config.
         '';
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index 28e2d18bf031..70c3ca50888c 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -33,36 +33,36 @@ in
 
     services.graylog = {
 
-      enable = mkEnableOption "Graylog";
+      enable = mkEnableOption (lib.mdDoc "Graylog");
 
       package = mkOption {
         type = types.package;
         default = pkgs.graylog;
         defaultText = literalExpression "pkgs.graylog";
-        description = "Graylog package to use.";
+        description = lib.mdDoc "Graylog package to use.";
       };
 
       user = mkOption {
         type = types.str;
         default = "graylog";
-        description = "User account under which graylog runs";
+        description = lib.mdDoc "User account under which graylog runs";
       };
 
       isMaster = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether this is the master instance of your Graylog cluster";
+        description = lib.mdDoc "Whether this is the master instance of your Graylog cluster";
       };
 
       nodeIdFile = mkOption {
         type = types.str;
         default = "/var/lib/graylog/server/node-id";
-        description = "Path of the file containing the graylog node-id";
+        description = lib.mdDoc "Path of the file containing the graylog node-id";
       };
 
       passwordSecret = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           You MUST set a secret to secure/pepper the stored user passwords here. Use at least 64 characters.
           Generate one by using for example: pwgen -N 1 -s 96
         '';
@@ -71,13 +71,13 @@ in
       rootUsername = mkOption {
         type = types.str;
         default = "admin";
-        description = "Name of the default administrator user";
+        description = lib.mdDoc "Name of the default administrator user";
       };
 
       rootPasswordSha2 = mkOption {
         type = types.str;
         example = "e3c652f0ba0b4801205814f8b6bc49672c4c74e25b497770bb89b22cdeb4e952";
-        description = ''
+        description = lib.mdDoc ''
           You MUST specify a hash password for the root user (which you only need to initially set up the
           system and in case you lose connectivity to your authentication backend)
           This password cannot be changed using the API or via the web interface. If you need to change it,
@@ -90,29 +90,29 @@ in
       elasticsearchHosts = mkOption {
         type = types.listOf types.str;
         example = literalExpression ''[ "http://node1:9200" "http://user:password@node2:19200" ]'';
-        description = "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
+        description = lib.mdDoc "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
       };
 
       messageJournalDir = mkOption {
         type = types.str;
         default = "/var/lib/graylog/data/journal";
-        description = "The directory which will be used to store the message journal. The directory must be exclusively used by Graylog and must not contain any other files than the ones created by Graylog itself";
+        description = lib.mdDoc "The directory which will be used to store the message journal. The directory must be exclusively used by Graylog and must not contain any other files than the ones created by Graylog itself";
       };
 
       mongodbUri = mkOption {
         type = types.str;
         default = "mongodb://localhost/graylog";
-        description = "MongoDB connection string. See http://docs.mongodb.org/manual/reference/connection-string/ for details";
+        description = lib.mdDoc "MongoDB connection string. See http://docs.mongodb.org/manual/reference/connection-string/ for details";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Any other configuration options you might want to add";
+        description = lib.mdDoc "Any other configuration options you might want to add";
       };
 
       plugins = mkOption {
-        description = "Extra graylog plugins";
+        description = lib.mdDoc "Extra graylog plugins";
         default = [ ];
         type = types.listOf types.package;
       };
diff --git a/nixos/modules/services/logging/heartbeat.nix b/nixos/modules/services/logging/heartbeat.nix
index 56fb4deabda5..a9ae11ec66e6 100644
--- a/nixos/modules/services/logging/heartbeat.nix
+++ b/nixos/modules/services/logging/heartbeat.nix
@@ -18,24 +18,34 @@ in
 
     services.heartbeat = {
 
-      enable = mkEnableOption "heartbeat";
+      enable = mkEnableOption (lib.mdDoc "heartbeat");
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.heartbeat;
+        defaultText = literalExpression "pkgs.heartbeat";
+        example = literalExpression "pkgs.heartbeat7";
+        description = lib.mdDoc ''
+          The heartbeat package to use.
+        '';
+      };
 
       name = mkOption {
         type = types.str;
         default = "heartbeat";
-        description = "Name of the beat";
+        description = lib.mdDoc "Name of the beat";
       };
 
       tags = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Tags to place on the shipped log messages";
+        description = lib.mdDoc "Tags to place on the shipped log messages";
       };
 
       stateDir = mkOption {
         type = types.str;
         default = "/var/lib/heartbeat";
-        description = "The state directory. heartbeat's own logs and other data are stored here.";
+        description = lib.mdDoc "The state directory. heartbeat's own logs and other data are stored here.";
       };
 
       extraConfig = mkOption {
@@ -46,7 +56,7 @@ in
             urls: ["http://localhost:9200"]
             schedule: '@every 10s'
         '';
-        description = "Any other configuration options you want to add";
+        description = lib.mdDoc "Any other configuration options you want to add";
       };
 
     };
@@ -67,7 +77,7 @@ in
       serviceConfig = {
         User = "nobody";
         AmbientCapabilities = "cap_net_raw";
-        ExecStart = "${pkgs.heartbeat}/bin/heartbeat -c \"${heartbeatYml}\" -path.data \"${cfg.stateDir}/data\" -path.logs \"${cfg.stateDir}/logs\"";
+        ExecStart = "${cfg.package}/bin/heartbeat -c \"${heartbeatYml}\" -path.data \"${cfg.stateDir}/data\" -path.logs \"${cfg.stateDir}/logs\"";
       };
     };
   };
diff --git a/nixos/modules/services/logging/journalbeat.nix b/nixos/modules/services/logging/journalbeat.nix
index 4035ab48b4b8..e761380552de 100644
--- a/nixos/modules/services/logging/journalbeat.nix
+++ b/nixos/modules/services/logging/journalbeat.nix
@@ -18,13 +18,13 @@ in
 
     services.journalbeat = {
 
-      enable = mkEnableOption "journalbeat";
+      enable = mkEnableOption (lib.mdDoc "journalbeat");
 
       package = mkOption {
         type = types.package;
         default = pkgs.journalbeat;
         defaultText = literalExpression "pkgs.journalbeat";
-        description = ''
+        description = lib.mdDoc ''
           The journalbeat package to use
         '';
       };
@@ -32,20 +32,20 @@ in
       name = mkOption {
         type = types.str;
         default = "journalbeat";
-        description = "Name of the beat";
+        description = lib.mdDoc "Name of the beat";
       };
 
       tags = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Tags to place on the shipped log messages";
+        description = lib.mdDoc "Tags to place on the shipped log messages";
       };
 
       stateDir = mkOption {
         type = types.str;
         default = "journalbeat";
-        description = ''
-          Directory below <literal>/var/lib/</literal> to store journalbeat's
+        description = lib.mdDoc ''
+          Directory below `/var/lib/` to store journalbeat's
           own logs and other data. This directory will be created automatically
           using systemd's StateDirectory mechanism.
         '';
@@ -54,7 +54,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Any other configuration options you want to add";
+        description = lib.mdDoc "Any other configuration options you want to add";
       };
 
     };
diff --git a/nixos/modules/services/logging/journaldriver.nix b/nixos/modules/services/logging/journaldriver.nix
index 9bd581e9ec0e..59eedff90d60 100644
--- a/nixos/modules/services/logging/journaldriver.nix
+++ b/nixos/modules/services/logging/journaldriver.nix
@@ -17,7 +17,7 @@ in {
     enable = mkOption {
       type        = types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable journaldriver to forward journald logs to
         Stackdriver Logging.
       '';
@@ -26,7 +26,7 @@ in {
     logLevel = mkOption {
       type        = types.str;
       default     = "info";
-      description = ''
+      description = lib.mdDoc ''
         Log level at which journaldriver logs its own output.
       '';
     };
@@ -34,7 +34,7 @@ in {
     logName = mkOption {
       type        = with types; nullOr str;
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         Configures the name of the target log in Stackdriver Logging.
         This option can be set to, for example, the hostname of a
         machine to improve the user experience in the logging
@@ -45,7 +45,7 @@ in {
     googleCloudProject = mkOption {
       type        = with types; nullOr str;
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         Configures the name of the Google Cloud project to which to
         forward journald logs.
 
@@ -57,7 +57,7 @@ in {
     logStream = mkOption {
       type        = with types; nullOr str;
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         Configures the name of the Stackdriver Logging log stream into
         which to write journald entries.
 
@@ -69,7 +69,7 @@ in {
     applicationCredentials = mkOption {
       type        = with types; nullOr path;
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to the service account private key (in JSON-format) used
         to forward log entries to Stackdriver Logging on non-GCP
         instances.
diff --git a/nixos/modules/services/logging/journalwatch.nix b/nixos/modules/services/logging/journalwatch.nix
index fb86904d1ea2..55e2d600ee4f 100644
--- a/nixos/modules/services/logging/journalwatch.nix
+++ b/nixos/modules/services/logging/journalwatch.nix
@@ -51,7 +51,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, periodically check the journal with journalwatch and report the results by mail.
         '';
       };
@@ -59,12 +59,12 @@ in {
       priority = mkOption {
         type = types.int;
         default = 6;
-        description = ''
+        description = lib.mdDoc ''
           Lowest priority of message to be considered.
           A value between 7 ("debug"), and 0 ("emerg"). Defaults to 6 ("info").
           If you don't care about anything with "info" priority, you can reduce
           this to e.g. 5 ("notice") to considerably reduce the amount of
-          messages without needing many <option>filterBlocks</option>.
+          messages without needing many {option}`filterBlocks`.
         '';
       };
 
@@ -75,7 +75,7 @@ in {
         type = types.str;
         default = "journalwatch@${config.networking.hostName}";
         defaultText = literalExpression ''"journalwatch@''${config.networking.hostName}"'';
-        description = ''
+        description = lib.mdDoc ''
           Mail address to send journalwatch reports from.
         '';
       };
@@ -83,7 +83,7 @@ in {
       mailTo = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Mail address to send journalwatch reports to.
         '';
       };
@@ -91,7 +91,7 @@ in {
       mailBinary = mkOption {
         type = types.path;
         default = "/run/wrappers/bin/sendmail";
-        description = ''
+        description = lib.mdDoc ''
           Sendmail-compatible binary to be used to send the messages.
         '';
       };
@@ -99,10 +99,10 @@ in {
       extraConfig = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the journalwatch/config configuration file.
           You can add any commandline argument to the config, without the '--'.
-          See <literal>journalwatch --help</literal> for all arguments and their description.
+          See `journalwatch --help` for all arguments and their description.
           '';
       };
 
@@ -112,12 +112,12 @@ in {
            match = mkOption {
               type = types.str;
               example = "SYSLOG_IDENTIFIER = systemd";
-              description = ''
-                Syntax: <literal>field = value</literal>
-                Specifies the log entry <literal>field</literal> this block should apply to.
-                If the <literal>field</literal> of a message matches this <literal>value</literal>,
-                this patternBlock's <option>filters</option> are applied.
-                If <literal>value</literal> starts and ends with a slash, it is interpreted as
+              description = lib.mdDoc ''
+                Syntax: `field = value`
+                Specifies the log entry `field` this block should apply to.
+                If the `field` of a message matches this `value`,
+                this patternBlock's {option}`filters` are applied.
+                If `value` starts and ends with a slash, it is interpreted as
                 an extended python regular expression, if not, it's an exact match.
                 The journal fields are explained in systemd.journal-fields(7).
               '';
@@ -129,8 +129,8 @@ in {
                 (Stopped|Stopping|Starting|Started) .*
                 (Reached target|Stopped target) .*
               '';
-              description = ''
-                The filters to apply on all messages which satisfy <option>match</option>.
+              description = lib.mdDoc ''
+                The filters to apply on all messages which satisfy {option}`match`.
                 Any of those messages that match any specified filter will be removed from journalwatch's output.
                 Each filter is an extended Python regular expression.
                 You can specify multiple filters and separate them by newlines.
@@ -175,7 +175,7 @@ in {
         ];
 
 
-        description = ''
+        description = lib.mdDoc ''
           filterBlocks can be defined to blacklist journal messages which are not errors.
           Each block matches on a log entry field, and the filters in that block then are matched
           against all messages with a matching log entry field.
@@ -191,7 +191,7 @@ in {
       interval = mkOption {
         type = types.str;
         default = "hourly";
-        description = ''
+        description = lib.mdDoc ''
           How often to run journalwatch.
 
           The format is described in systemd.time(7).
@@ -200,7 +200,7 @@ in {
       accuracy = mkOption {
         type = types.str;
         default = "10min";
-        description = ''
+        description = lib.mdDoc ''
           The time window around the interval in which the journalwatch run will be scheduled.
 
           The format is described in systemd.time(7).
@@ -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/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix
index c8738b734f9a..8a277cea6e46 100644
--- a/nixos/modules/services/logging/logcheck.nix
+++ b/nixos/modules/services/logging/logcheck.nix
@@ -56,7 +56,7 @@ let
   levelOption = mkOption {
     default = "server";
     type = types.enum [ "workstation" "server" "paranoid" ];
-    description = ''
+    description = lib.mdDoc ''
       Set the logcheck level.
     '';
   };
@@ -68,7 +68,7 @@ let
       regex = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Regex specifying which log lines to ignore.
         '';
       };
@@ -80,7 +80,7 @@ let
       user = mkOption {
         default = "root";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User that runs the cronjob.
         '';
       };
@@ -88,7 +88,7 @@ let
       cmdline = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Command line for the cron job. Will be turned into a regex for the logcheck ignore rule.
         '';
       };
@@ -97,7 +97,7 @@ let
         default = null;
         type = types.nullOr (types.str);
         example = "02 06 * * *";
-        description = ''
+        description = lib.mdDoc ''
           "min hr dom mon dow" crontab time args, to auto-create a cronjob too.
           Leave at null to not do this and just add a logcheck ignore rule.
         '';
@@ -109,18 +109,12 @@ in
 {
   options = {
     services.logcheck = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Enable the logcheck cron job.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "logcheck cron job");
 
       user = mkOption {
         default = "logcheck";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Username for the logcheck user.
         '';
       };
@@ -129,7 +123,7 @@ in
         default = "*";
         example = "6";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Time of day to run logcheck. A logcheck will be scheduled at xx:02 each day.
           Leave default (*) to run every hour. Of course when nothing special was logged,
           logcheck will be silent.
@@ -140,7 +134,7 @@ in
         default = "root";
         example = "you@domain.com";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Email address to send reports to.
         '';
       };
@@ -148,7 +142,7 @@ in
       level = mkOption {
         default = "server";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Set the logcheck level. Either "workstation", "server", or "paranoid".
         '';
       };
@@ -156,7 +150,7 @@ in
       config = mkOption {
         default = "FQDN=1";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Config options that you would like in logcheck.conf.
         '';
       };
@@ -165,7 +159,7 @@ in
         default = [ "/var/log/messages" ];
         type = types.listOf types.path;
         example = [ "/var/log/messages" "/var/log/mail" ];
-        description = ''
+        description = lib.mdDoc ''
           Which log files to check.
         '';
       };
@@ -174,14 +168,14 @@ in
         default = [];
         example = [ "/etc/logcheck" ];
         type = types.listOf types.path;
-        description = ''
+        description = lib.mdDoc ''
           Directories with extra rules.
         '';
       };
 
       ignore = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines extra ignore rules.
         '';
         type = with types; attrsOf (submodule ignoreOptions);
@@ -189,7 +183,7 @@ in
 
       ignoreCron = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines extra ignore rules for cronjobs.
         '';
         type = with types; attrsOf (submodule ignoreCronOptions);
@@ -199,7 +193,7 @@ in
         default = [];
         type = types.listOf types.str;
         example = [ "postdrop" "mongodb" ];
-        description = ''
+        description = lib.mdDoc ''
           Extra groups for the logcheck user, for example to be able to use sendmail,
           or to access certain log files.
         '';
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index e6eb0552c9e9..1799e9282b3b 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/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 = ''
-          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 = ''
-          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 = ''
-          The user account to use for rotation.
-        '';
-      };
-
-      group = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = ''
-          The group to use for rotation.
-        '';
-      };
-
-      frequency = mkOption {
-        type = types.enum [ "hourly" "daily" "weekly" "monthly" "yearly" ];
-        default = "daily";
-        description = ''
-          How often to rotate the logs.
-        '';
-      };
-
-      keep = mkOption {
-        type = types.int;
-        default = 20;
-        description = ''
-          How many rotations to keep.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Extra logrotate config options for this path. Refer to
-          <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
-        '';
-      };
-
-      priority = mkOption {
-        type = types.int;
-        default = 1000;
-        description = ''
-          Order of this logrotate block in relation to the others. The semantics are
-          the same as with `lib.mkOrder`. Smaller values have a greater priority.
-        '';
-      };
-    };
-
-    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; }; }
     ]
@@ -167,22 +56,23 @@ let
       sed -e "s/\bsu\s.*/su $user $group/" \
           -e "s/\b\(create\s\+[0-9]*\s*\|createolddir\s\+[0-9]*\s\+\).*/\1$user $group/" \
           -e "1imissingok" -e "s/\bnomissingok\b//" \
-          $out > /tmp/logrotate.conf
+          $out > logrotate.conf
       # Since this makes for very verbose builds only show real error.
       # There is no way to control log level, but logrotate hardcodes
       # 'error:' at common log level, so we can use grep, taking care
       # to keep error codes
       set -o pipefail
-      if ! ${pkgs.buildPackages.logrotate}/sbin/logrotate --debug /tmp/logrotate.conf 2>&1 \
-          | ( ! grep "error:" ) > /tmp/logrotate-error; then
+      if ! ${pkgs.buildPackages.logrotate}/sbin/logrotate -s logrotate.status \
+                      --debug logrotate.conf 2>&1 \
+                  | ( ! grep "error:" ) > logrotate-error; then
               echo "Logrotate configuration check failed."
               echo "The failing configuration (after adjustments to pass tests in sandbox) was:"
               printf "%s\n" "-------"
-              cat /tmp/logrotate.conf
+              cat logrotate.conf
               printf "%s\n" "-------"
               echo "The error reported by logrotate was as follow:"
               printf "%s\n" "-------"
-              cat /tmp/logrotate-error
+              cat logrotate-error
               printf "%s\n" "-------"
               echo "You can disable this check with services.logrotate.checkConfig = false,"
               echo "but if you think it should work please report this failure along with"
@@ -193,42 +83,63 @@ let
   };
 
   mailOption =
-    if foldr (n: a: a || n ? mail) false (attrValues cfg.settings)
+    if foldr (n: a: a || (n.mail or false) != false) false (attrValues cfg.settings)
     then "--mail=${pkgs.mailutils}/bin/mail"
     else "";
 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 != {}";
       };
 
       settings = mkOption {
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           logrotate freeform settings: each attribute here will define its own section,
           ordered by priority, which can either define files to rotate with their settings
           or settings common to all further files settings.
-          Refer to <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+          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;
             };
 
             global = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether this setting is a global option or not: set to have these
                 settings apply to all files settings with a higher priority.
               '';
@@ -239,7 +150,7 @@ in
               defaultText = ''
                 The attrset name if not specified
               '';
-              description = ''
+              description = lib.mdDoc ''
                 Single or list of files for which rules are defined.
                 The files are quoted with double-quotes in logrotate configuration,
                 so globs and spaces are supported.
@@ -250,16 +161,16 @@ in
             frequency = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              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.
               '';
@@ -275,9 +186,9 @@ in
         defaultText = ''
           A configuration file automatically generated by NixOS.
         '';
-        description = ''
+        description = lib.mdDoc ''
           Override the configuration file used by MySQL. By default,
-          NixOS generates one automatically from <xref linkend="opt-services.logrotate.settings"/>.
+          NixOS generates one automatically from [](#opt-services.logrotate.settings).
         '';
         example = literalExpression ''
           pkgs.writeText "logrotate.conf" '''
@@ -293,7 +204,7 @@ in
       checkConfig = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the config should be checked at build time.
 
           Some options are not checkable at build time because of the build sandbox:
@@ -303,83 +214,17 @@ in
           and users are replaced by dummy users), so tests are complemented by a
           logrotate-checkconf service that is enabled by default.
           This extra check can be disabled by disabling it at the systemd level with the
-          <option>services.systemd.services.logrotate-checkconf.enable</option> option.
+          {option}`services.systemd.services.logrotate-checkconf.enable` option.
 
           Conversely there are still things that might make this check fail incorrectly
           (e.g. a file path where we don't have access to intermediate directories):
           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 = ''
-          Extra contents to append to the logrotate configuration file. Refer to
-          <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
-          This setting has been deprecated in favor of
-          <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
-        '';
-      };
     };
   };
 
   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/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix
index a08203dffe78..e9e3ae1f14ce 100644
--- a/nixos/modules/services/logging/logstash.nix
+++ b/nixos/modules/services/logging/logstash.nix
@@ -51,27 +51,27 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable logstash.";
+        description = lib.mdDoc "Enable logstash.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.logstash;
         defaultText = literalExpression "pkgs.logstash";
-        description = "Logstash package to use.";
+        description = lib.mdDoc "Logstash package to use.";
       };
 
       plugins = mkOption {
         type = types.listOf types.path;
         default = [ ];
         example = literalExpression "[ pkgs.logstash-contrib ]";
-        description = "The paths to find other logstash plugins in.";
+        description = lib.mdDoc "The paths to find other logstash plugins in.";
       };
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/logstash";
-        description = ''
+        description = lib.mdDoc ''
           A path to directory writable by logstash that it uses to store data.
           Plugins will also have access to this path.
         '';
@@ -80,36 +80,36 @@ in
       logLevel = mkOption {
         type = types.enum [ "debug" "info" "warn" "error" "fatal" ];
         default = "warn";
-        description = "Logging verbosity level.";
+        description = lib.mdDoc "Logging verbosity level.";
       };
 
       filterWorkers = mkOption {
         type = types.int;
         default = 1;
-        description = "The quantity of filter workers to run.";
+        description = lib.mdDoc "The quantity of filter workers to run.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "Address on which to start webserver.";
+        description = lib.mdDoc "Address on which to start webserver.";
       };
 
       port = mkOption {
         type = types.str;
         default = "9292";
-        description = "Port on which to start webserver.";
+        description = lib.mdDoc "Port on which to start webserver.";
       };
 
       inputConfig = mkOption {
         type = types.lines;
         default = "generator { }";
-        description = "Logstash input configuration.";
+        description = lib.mdDoc "Logstash input configuration.";
         example = literalExpression ''
           '''
             # Read from journal
             pipe {
-              command => "''${pkgs.systemd}/bin/journalctl -f -o json"
+              command => "''${config.systemd.package}/bin/journalctl -f -o json"
               type => "syslog" codec => json {}
             }
           '''
@@ -119,7 +119,7 @@ in
       filterConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "logstash filter configuration.";
+        description = lib.mdDoc "logstash filter configuration.";
         example = ''
           if [type] == "syslog" {
             # Keep only relevant systemd fields
@@ -137,7 +137,7 @@ in
       outputConfig = mkOption {
         type = types.lines;
         default = "stdout { codec => rubydebug }";
-        description = "Logstash output configuration.";
+        description = lib.mdDoc "Logstash output configuration.";
         example = ''
           redis { host => ["localhost"] data_type => "list" key => "logstash" codec => json }
           elasticsearch { }
@@ -147,7 +147,7 @@ in
       extraSettings = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra Logstash settings in YAML format.";
+        description = lib.mdDoc "Extra Logstash settings in YAML format.";
         example = ''
           pipeline:
             batch:
@@ -159,7 +159,7 @@ in
       extraJvmOptions = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra JVM options, one per line (jvm.options format).";
+        description = lib.mdDoc "Extra JVM options, one per line (jvm.options format).";
         example = ''
           -Xms2g
           -Xmx2g
diff --git a/nixos/modules/services/logging/promtail.nix b/nixos/modules/services/logging/promtail.nix
index a34bc07b6ab2..9db82fd42b28 100644
--- a/nixos/modules/services/logging/promtail.nix
+++ b/nixos/modules/services/logging/promtail.nix
@@ -12,12 +12,12 @@ 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 {
       type = (pkgs.formats.json {}).type;
-      description = ''
+      description = lib.mdDoc ''
         Specify the configuration for Promtail in Nix.
       '';
     };
@@ -26,7 +26,7 @@ in {
       type = listOf str;
       default = [];
       example = [ "--server.http-listen-port=3101" ];
-      description = ''
+      description = lib.mdDoc ''
         Specify a list of additional command line flags,
         which get escaped and are then passed to Loki.
       '';
diff --git a/nixos/modules/services/logging/rsyslogd.nix b/nixos/modules/services/logging/rsyslogd.nix
index b924d94e0b0d..207d416c1a88 100644
--- a/nixos/modules/services/logging/rsyslogd.nix
+++ b/nixos/modules/services/logging/rsyslogd.nix
@@ -39,7 +39,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable syslogd.  Note that systemd also logs
           syslog messages, so you normally don't need to run syslogd.
         '';
@@ -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`.
         '';
       };
 
@@ -69,8 +69,8 @@ in
         type = types.listOf types.str;
         default = [ ];
         example = [ "-m 0" ];
-        description = ''
-          Additional parameters passed to <command>rsyslogd</command>.
+        description = lib.mdDoc ''
+          Additional parameters passed to {command}`rsyslogd`.
         '';
       };
 
diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix
index 0a57bf20bd07..d22acbeaa70c 100644
--- a/nixos/modules/services/logging/syslog-ng.nix
+++ b/nixos/modules/services/logging/syslog-ng.nix
@@ -36,7 +36,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the syslog-ng daemon.
         '';
       };
@@ -44,27 +44,24 @@ in {
         type = types.package;
         default = pkgs.syslogng;
         defaultText = literalExpression "pkgs.syslogng";
-        description = ''
+        description = lib.mdDoc ''
           The package providing syslog-ng binaries.
         '';
       };
       extraModulePaths = mkOption {
         type = types.listOf types.str;
         default = [];
-        example = literalExpression ''
-          [ "''${pkgs.syslogng_incubator}/lib/syslog-ng" ]
-        '';
-        description = ''
+        description = lib.mdDoc ''
           A list of paths that should be included in syslog-ng's
-          <literal>--module-path</literal> option. They should usually
-          end in <literal>/lib/syslog-ng</literal>
+          `--module-path` option. They should usually
+          end in `/lib/syslog-ng`
         '';
       };
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Configuration added to the end of <literal>syslog-ng.conf</literal>.
+        description = lib.mdDoc ''
+          Configuration added to the end of `syslog-ng.conf`.
         '';
       };
       configHeader = mkOption {
@@ -73,7 +70,7 @@ in {
           @version: 3.6
           @include "scl.conf"
         '';
-        description = ''
+        description = lib.mdDoc ''
           The very first lines of the configuration file. Should usually contain
           the syslog-ng version header.
         '';
diff --git a/nixos/modules/services/logging/syslogd.nix b/nixos/modules/services/logging/syslogd.nix
index fe0b0490811d..43969402588d 100644
--- a/nixos/modules/services/logging/syslogd.nix
+++ b/nixos/modules/services/logging/syslogd.nix
@@ -39,7 +39,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable syslogd.  Note that systemd also logs
           syslog messages, so you normally don't need to run syslogd.
         '';
@@ -48,7 +48,7 @@ in
       tty = mkOption {
         type = types.str;
         default = "tty10";
-        description = ''
+        description = lib.mdDoc ''
           The tty device on which syslogd will print important log
           messages. Leave this option blank to disable tty logging.
         '';
@@ -57,17 +57,17 @@ 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`.
         '';
       };
 
       enableNetworkInput = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Accept logging through UDP. Option -r of syslogd(8).
         '';
       };
@@ -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`.
         '';
       };
 
@@ -86,8 +86,8 @@ in
         type = types.listOf types.str;
         default = [ ];
         example = [ "-m 0" ];
-        description = ''
-          Additional parameters passed to <command>syslogd</command>.
+        description = lib.mdDoc ''
+          Additional parameters passed to {command}`syslogd`.
         '';
       };
 
diff --git a/nixos/modules/services/logging/vector.nix b/nixos/modules/services/logging/vector.nix
index be36b2a41bba..1803ea85e49c 100644
--- a/nixos/modules/services/logging/vector.nix
+++ b/nixos/modules/services/logging/vector.nix
@@ -6,12 +6,12 @@ let cfg = config.services.vector;
 in
 {
   options.services.vector = {
-    enable = mkEnableOption "Vector";
+    enable = mkEnableOption (lib.mdDoc "Vector");
 
     journaldAccess = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable Vector to access journald.
       '';
     };
@@ -19,7 +19,7 @@ in
     settings = mkOption {
       type = (pkgs.formats.json { }).type;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Specify the configuration for Vector in Nix.
       '';
     };
@@ -43,8 +43,10 @@ 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
diff --git a/nixos/modules/services/mail/clamsmtp.nix b/nixos/modules/services/mail/clamsmtp.nix
index fc1267c5d280..a0de25962845 100644
--- a/nixos/modules/services/mail/clamsmtp.nix
+++ b/nixos/modules/services/mail/clamsmtp.nix
@@ -12,17 +12,17 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable clamsmtp.";
+        description = lib.mdDoc "Whether to enable clamsmtp.";
       };
 
       instances = mkOption {
-        description = "Instances of clamsmtp to run.";
+        description = lib.mdDoc "Instances of clamsmtp to run.";
         type = types.listOf (types.submodule { options = {
           action = mkOption {
             type = types.enum [ "bounce" "drop" "pass" ];
             default = "drop";
             description =
-              ''
+              lib.mdDoc ''
                 Action to take when a virus is detected.
 
                 Note that viruses often spoof sender addresses, so bouncing is
@@ -35,7 +35,7 @@ in
             default = "";
             example = "X-Virus-Scanned: ClamAV using ClamSMTP";
             description =
-              ''
+              lib.mdDoc ''
                 A header to add to scanned messages. See clamsmtpd.conf(5) for
                 more details. Empty means no header.
               '';
@@ -45,7 +45,7 @@ in
             type = types.int;
             default = 0;
             description =
-              ''
+              lib.mdDoc ''
                 Number of seconds to wait between each NOOP sent to the sending
                 server. 0 to disable.
 
@@ -58,7 +58,7 @@ in
             type = types.str;
             example = "127.0.0.1:10025";
             description =
-              ''
+              lib.mdDoc ''
                 Address to wait for incoming SMTP connections on. See
                 clamsmtpd.conf(5) for more details.
               '';
@@ -68,7 +68,7 @@ in
             type = types.bool;
             default = false;
             description =
-              ''
+              lib.mdDoc ''
                 Whether to quarantine files that contain viruses by leaving them
                 in the temporary directory.
               '';
@@ -77,13 +77,13 @@ in
           maxConnections = mkOption {
             type = types.int;
             default = 64;
-            description = "Maximum number of connections to accept at once.";
+            description = lib.mdDoc "Maximum number of connections to accept at once.";
           };
 
           outAddress = mkOption {
             type = types.str;
             description =
-              ''
+              lib.mdDoc ''
                 Address of the SMTP server to send email to once it has been
                 scanned.
               '';
@@ -93,7 +93,7 @@ in
             type = types.str;
             default = "/tmp";
             description =
-              ''
+              lib.mdDoc ''
                 Temporary directory that needs to be accessible to both clamd
                 and clamsmtpd.
               '';
@@ -102,20 +102,20 @@ in
           timeout = mkOption {
             type = types.int;
             default = 180;
-            description = "Time-out for network connections.";
+            description = lib.mdDoc "Time-out for network connections.";
           };
 
           transparentProxy = mkOption {
             type = types.bool;
             default = false;
-            description = "Enable clamsmtp's transparent proxy support.";
+            description = lib.mdDoc "Enable clamsmtp's transparent proxy support.";
           };
 
           virusAction = mkOption {
             type = with types; nullOr path;
             default = null;
             description =
-              ''
+              lib.mdDoc ''
                 Command to run when a virus is found. Please see VIRUS ACTION in
                 clamsmtpd(8) for a discussion of this option and its safe use.
               '';
@@ -125,7 +125,7 @@ in
             type = types.bool;
             default = false;
             description =
-              ''
+              lib.mdDoc ''
                 Send the XCLIENT command to the receiving server, for forwarding
                 client addresses and connection information if the receiving
                 server supports this feature.
diff --git a/nixos/modules/services/mail/davmail.nix b/nixos/modules/services/mail/davmail.nix
index e9f31e6fb390..483f591a7268 100644
--- a/nixos/modules/services/mail/davmail.nix
+++ b/nixos/modules/services/mail/davmail.nix
@@ -25,21 +25,21 @@ 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;
-        description = "Outlook Web Access URL to access the exchange server, i.e. the base webmail URL.";
+        description = lib.mdDoc "Outlook Web Access URL to access the exchange server, i.e. the base webmail URL.";
         example = "https://outlook.office365.com/EWS/Exchange.asmx";
       };
 
       config = mkOption {
         type = configType;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Davmail configuration. Refer to
-          <link xlink:href="http://davmail.sourceforge.net/serversetup.html"/>
-          and <link xlink:href="http://davmail.sourceforge.net/advanced.html"/>
+          <http://davmail.sourceforge.net/serversetup.html>
+          and <http://davmail.sourceforge.net/advanced.html>
           for details on supported values.
         '';
         example = literalExpression ''
diff --git a/nixos/modules/services/mail/dkimproxy-out.nix b/nixos/modules/services/mail/dkimproxy-out.nix
index f4ac9e47007a..6f9cbc4e9d4d 100644
--- a/nixos/modules/services/mail/dkimproxy-out.nix
+++ b/nixos/modules/services/mail/dkimproxy-out.nix
@@ -15,7 +15,7 @@ in
         type = types.bool;
         default = false;
         description =
-          ''
+          lib.mdDoc ''
             Whether to enable dkimproxy_out.
 
             Note that a key will be auto-generated, and can be found in
@@ -26,26 +26,26 @@ in
       listen = mkOption {
         type = types.str;
         example = "127.0.0.1:10027";
-        description = "Address:port DKIMproxy should listen on.";
+        description = lib.mdDoc "Address:port DKIMproxy should listen on.";
       };
 
       relay = mkOption {
         type = types.str;
         example = "127.0.0.1:10028";
-        description = "Address:port DKIMproxy should forward mail to.";
+        description = lib.mdDoc "Address:port DKIMproxy should forward mail to.";
       };
 
       domains = mkOption {
         type = with types; listOf str;
         example = [ "example.org" "example.com" ];
-        description = "List of domains DKIMproxy can sign for.";
+        description = lib.mdDoc "List of domains DKIMproxy can sign for.";
       };
 
       selector = mkOption {
         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
@@ -59,7 +59,7 @@ in
         type = types.int;
         default = 2048;
         description =
-          ''
+          lib.mdDoc ''
             Size of the RSA key to use to sign outgoing emails. Note that the
             maximum mandatorily verified as per RFC6376 is 2048.
           '';
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index a8c1f176782c..f6a167572f72 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -137,25 +137,25 @@ let
         example = "Spam";
         default = name;
         readOnly = true;
-        description = "The name of the mailbox.";
+        description = lib.mdDoc "The name of the mailbox.";
       };
       auto = mkOption {
         type = types.enum [ "no" "create" "subscribe" ];
         default = "no";
         example = "subscribe";
-        description = "Whether to automatically create or create and subscribe to the mailbox or not.";
+        description = lib.mdDoc "Whether to automatically create or create and subscribe to the mailbox or not.";
       };
       specialUse = mkOption {
         type = types.nullOr (types.enum [ "All" "Archive" "Drafts" "Flagged" "Junk" "Sent" "Trash" ]);
         default = null;
         example = "Junk";
-        description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
+        description = lib.mdDoc "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
       };
       autoexpunge = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "60d";
-        description = ''
+        description = lib.mdDoc ''
           To automatically remove all email from the mailbox which is older than the
           specified time.
         '';
@@ -169,37 +169,37 @@ 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;
       default = [];
-      description = "Additional listeners to start when Dovecot is enabled.";
+      description = lib.mdDoc "Additional listeners to start when Dovecot is enabled.";
     };
 
     user = mkOption {
       type = types.str;
       default = "dovecot2";
-      description = "Dovecot user name.";
+      description = lib.mdDoc "Dovecot user name.";
     };
 
     group = mkOption {
       type = types.str;
       default = "dovecot2";
-      description = "Dovecot group name.";
+      description = lib.mdDoc "Dovecot group name.";
     };
 
     extraConfig = mkOption {
       type = types.lines;
       default = "";
       example = "mail_debug = yes";
-      description = "Additional entries to put verbatim into Dovecot's config file.";
+      description = lib.mdDoc "Additional entries to put verbatim into Dovecot's config file.";
     };
 
     mailPlugins =
@@ -209,7 +209,7 @@ in
             enable = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = "mail plugins to enable as a list of strings to append to the ${hint} <literal>$mail_plugins</literal> configuration variable";
+              description = lib.mdDoc "mail plugins to enable as a list of strings to append to the ${hint} `$mail_plugins` configuration variable";
             };
           };
         };
@@ -218,20 +218,20 @@ in
           type = with types; submodule {
             options = {
               globally = mkOption {
-                description = "Additional entries to add to the mail_plugins variable for all protocols";
+                description = lib.mdDoc "Additional entries to add to the mail_plugins variable for all protocols";
                 type = plugins "top-level";
                 example = { enable = [ "virtual" ]; };
                 default = { enable = []; };
               };
               perProtocol = mkOption {
-                description = "Additional entries to add to the mail_plugins variable, per protocol";
+                description = lib.mdDoc "Additional entries to add to the mail_plugins variable, per protocol";
                 type = attrsOf (plugins "corresponding per-protocol");
                 default = {};
                 example = { imap = [ "imap_acl" ]; };
               };
             };
           };
-          description = "Additional entries to add to the mail_plugins variable, globally and per protocol";
+          description = lib.mdDoc "Additional entries to add to the mail_plugins variable, globally and per protocol";
           example = {
             globally.enable = [ "acl" ];
             perProtocol.imap.enable = [ "imap_acl" ];
@@ -242,7 +242,7 @@ in
     configFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = "Config file used for the whole dovecot configuration.";
+      description = lib.mdDoc "Config file used for the whole dovecot configuration.";
       apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf;
     };
 
@@ -250,7 +250,7 @@ in
       type = types.str;
       default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */
       example = "maildir:~/mail:INBOX=/var/spool/mail/%u";
-      description = ''
+      description = lib.mdDoc ''
         Location that dovecot will use for mail folders. Dovecot mail_location option.
       '';
     };
@@ -258,24 +258,24 @@ in
     mailUser = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Default user to store mail for virtual users.";
+      description = lib.mdDoc "Default user to store mail for virtual users.";
     };
 
     mailGroup = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Default group to store mail for virtual users.";
+      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;
       default = [];
       example = literalExpression "[ pkgs.dovecot_pigeonhole ]";
-      description = ''
+      description = lib.mdDoc ''
         Symlinks the contents of lib/dovecot of every given package into
         /etc/dovecot/modules. This will make the given modules available
         if a dovecot package with the module_dir patch applied is being used.
@@ -285,32 +285,32 @@ in
     sslCACert = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Path to the server's CA certificate key.";
+      description = lib.mdDoc "Path to the server's CA certificate key.";
     };
 
     sslServerCert = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Path to the server's public key.";
+      description = lib.mdDoc "Path to the server's public key.";
     };
 
     sslServerKey = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Path to the server's private key.";
+      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;
       default = {};
-      description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
+      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
@@ -323,15 +323,15 @@ in
           Spam = { specialUse = "Junk"; auto = "create"; };
         }
       '';
-      description = "Configure mailboxes and auto create or subscribe them.";
+      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;
       default = "12340";
-      description = ''
+      description = lib.mdDoc ''
         The Port the dovecot quota service binds to.
         If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config.
       '';
@@ -340,7 +340,7 @@ in
       type = types.str;
       default = "100G";
       example = "10G";
-      description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
+      description = lib.mdDoc "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
     };
 
   };
diff --git a/nixos/modules/services/mail/dspam.nix b/nixos/modules/services/mail/dspam.nix
index 766ebc8095a0..4fccd452a4fe 100644
--- a/nixos/modules/services/mail/dspam.nix
+++ b/nixos/modules/services/mail/dspam.nix
@@ -38,43 +38,43 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the dspam spam filter.";
+        description = lib.mdDoc "Whether to enable the dspam spam filter.";
       };
 
       user = mkOption {
         type = types.str;
         default = "dspam";
-        description = "User for the dspam daemon.";
+        description = lib.mdDoc "User for the dspam daemon.";
       };
 
       group = mkOption {
         type = types.str;
         default = "dspam";
-        description = "Group for the dspam daemon.";
+        description = lib.mdDoc "Group for the dspam daemon.";
       };
 
       storageDriver = mkOption {
         type = types.str;
         default = "hash";
-        description =  "Storage driver backend to use for dspam.";
+        description =  lib.mdDoc "Storage driver backend to use for dspam.";
       };
 
       domainSocket = mkOption {
         type = types.nullOr types.path;
         default = defaultSock;
-        description = "Path to local domain socket which is used for communication with the daemon. Set to null to disable UNIX socket.";
+        description = lib.mdDoc "Path to local domain socket which is used for communication with the daemon. Set to null to disable UNIX socket.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional dspam configuration.";
+        description = lib.mdDoc "Additional dspam configuration.";
       };
 
       maintenanceInterval = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "If set, maintenance script will be run at specified (in systemd.timer format) interval";
+        description = lib.mdDoc "If set, maintenance script will be run at specified (in systemd.timer format) interval";
       };
 
     };
diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix
index 7356db2b6a62..cd0da4fc5098 100644
--- a/nixos/modules/services/mail/exim.nix
+++ b/nixos/modules/services/mail/exim.nix
@@ -17,13 +17,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Exim mail transfer agent.";
+        description = lib.mdDoc "Whether to enable the Exim mail transfer agent.";
       };
 
       config = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Verbatim Exim configuration.  This should not contain exim_user,
           exim_group, exim_path, or spool_directory.
         '';
@@ -32,7 +32,7 @@ in
       user = mkOption {
         type = types.str;
         default = "exim";
-        description = ''
+        description = lib.mdDoc ''
           User to use when no root privileges are required.
           In particular, this applies when receiving messages and when doing
           remote deliveries.  (Local deliveries run as various non-root users,
@@ -44,7 +44,7 @@ in
       group = mkOption {
         type = types.str;
         default = "exim";
-        description = ''
+        description = lib.mdDoc ''
           Group to use when no root privileges are required.
         '';
       };
@@ -52,7 +52,7 @@ in
       spoolDir = mkOption {
         type = types.path;
         default = "/var/spool/exim";
-        description = ''
+        description = lib.mdDoc ''
           Location of the spool directory of exim.
         '';
       };
@@ -61,7 +61,7 @@ in
         type = types.package;
         default = pkgs.exim;
         defaultText = literalExpression "pkgs.exim";
-        description = ''
+        description = lib.mdDoc ''
           The Exim derivation to use.
           This can be used to enable features such as LDAP or PAM support.
         '';
@@ -70,7 +70,7 @@ in
       queueRunnerInterval = mkOption {
         type = types.str;
         default = "5m";
-        description = ''
+        description = lib.mdDoc ''
           How often to spawn a new queue runner.
         '';
       };
diff --git a/nixos/modules/services/mail/listmonk.nix b/nixos/modules/services/mail/listmonk.nix
new file mode 100644
index 000000000000..8b636bd5b1ff
--- /dev/null
+++ b/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 = mkPackageOption 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/nixos/modules/services/mail/maddy.nix b/nixos/modules/services/mail/maddy.nix
index 0b06905ac6f1..eeb113e204c6 100644
--- a/nixos/modules/services/mail/maddy.nix
+++ b/nixos/modules/services/mail/maddy.nix
@@ -139,33 +139,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>
+          :::
         '';
       };
 
@@ -173,7 +173,7 @@ in {
         default = "localhost";
         type = with types; uniq string;
         example = ''example.com'';
-        description = ''
+        description = lib.mdDoc ''
           Hostname to use. It should be FQDN.
         '';
       };
@@ -182,7 +182,7 @@ in {
         default = "localhost";
         type = with types; uniq string;
         example = ''mail.example.com'';
-        description = ''
+        description = lib.mdDoc ''
           Primary MX domain to use. It should be FQDN.
         '';
       };
@@ -195,7 +195,7 @@ in {
           "example.com"
           "other.example.com"
         ];
-        description = ''
+        description = lib.mdDoc ''
           Define list of allowed domains.
         '';
       };
@@ -203,21 +203,22 @@ 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>
+
+          ::: {.note}
           This should not be used in a production environment.
-          </para></note>
+          :::
         '';
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open the configured incoming and outgoing mail server ports.
         '';
       };
diff --git a/nixos/modules/services/mail/mail.nix b/nixos/modules/services/mail/mail.nix
index fcc7ff6db91b..8e1424595b51 100644
--- a/nixos/modules/services/mail/mail.nix
+++ b/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/nixos/modules/services/mail/mailcatcher.nix b/nixos/modules/services/mail/mailcatcher.nix
index 84f06ed199dc..d0f4550c1926 100644
--- a/nixos/modules/services/mail/mailcatcher.nix
+++ b/nixos/modules/services/mail/mailcatcher.nix
@@ -11,37 +11,37 @@ in
   options = {
 
     services.mailcatcher = {
-      enable = mkEnableOption "MailCatcher";
+      enable = mkEnableOption (lib.mdDoc "MailCatcher");
 
       http.ip = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "The ip address of the http server.";
+        description = lib.mdDoc "The ip address of the http server.";
       };
 
       http.port = mkOption {
         type = types.port;
         default = 1080;
-        description = "The port address of the http server.";
+        description = lib.mdDoc "The port address of the http server.";
       };
 
       http.path = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "Prefix to all HTTP paths.";
+        description = lib.mdDoc "Prefix to all HTTP paths.";
         example = "/mailcatcher";
       };
 
       smtp.ip = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "The ip address of the smtp server.";
+        description = lib.mdDoc "The ip address of the smtp server.";
       };
 
       smtp.port = mkOption {
         type = types.port;
         default = 1025;
-        description = "The port address of the smtp server.";
+        description = lib.mdDoc "The port address of the smtp server.";
       };
     };
 
diff --git a/nixos/modules/services/mail/mailhog.nix b/nixos/modules/services/mail/mailhog.nix
index b113f4ff3dec..7ae62de291ba 100644
--- a/nixos/modules/services/mail/mailhog.nix
+++ b/nixos/modules/services/mail/mailhog.nix
@@ -27,36 +27,36 @@ in
   options = {
 
     services.mailhog = {
-      enable = mkEnableOption "MailHog";
+      enable = mkEnableOption (lib.mdDoc "MailHog");
 
       storage = mkOption {
         type = types.enum [ "maildir" "memory" ];
         default = "memory";
-        description = "Store mails on disk or in memory.";
+        description = lib.mdDoc "Store mails on disk or in memory.";
       };
 
       apiPort = mkOption {
         type = types.port;
         default = 8025;
-        description = "Port on which the API endpoint will listen.";
+        description = lib.mdDoc "Port on which the API endpoint will listen.";
       };
 
       smtpPort = mkOption {
         type = types.port;
         default = 1025;
-        description = "Port on which the SMTP endpoint will listen.";
+        description = lib.mdDoc "Port on which the SMTP endpoint will listen.";
       };
 
       uiPort = mkOption {
         type = types.port;
         default = 8025;
-        description = "Port on which the HTTP UI will listen.";
+        description = lib.mdDoc "Port on which the HTTP UI will listen.";
       };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "List of additional arguments to pass to the MailHog process.";
+        description = lib.mdDoc "List of additional arguments to pass to the MailHog process.";
       };
     };
   };
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index f1e074587b31..0ca87696b143 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -6,10 +6,10 @@ let
 
   cfg = config.services.mailman;
 
-  pythonEnv = pkgs.python3.withPackages (ps:
-    [ps.mailman ps.mailman-web]
-    ++ lib.optional cfg.hyperkitty.enable ps.mailman-hyperkitty
-    ++ cfg.extraPythonPackages);
+  inherit (pkgs.mailmanPackages.buildEnvs { withHyperkitty = cfg.hyperkitty.enable; withLDAP = cfg.ldap.enable; })
+    mailmanEnv webEnv;
+
+  withPostgresql = config.services.postgresql.enable;
 
   # This deliberately doesn't use recursiveUpdate so users can
   # override the defaults.
@@ -44,7 +44,13 @@ let
     transport_file_type: hash
   '';
 
-  mailmanCfg = lib.generators.toINI {} cfg.settings;
+  mailmanCfg = lib.generators.toINI {}
+    (recursiveUpdate cfg.settings
+      ((optionalAttrs (cfg.restApiPassFile != null) {
+        webservice.admin_pass = "#NIXOS_MAILMAN_REST_API_PASS_SECRET#";
+      })));
+
+  mailmanCfgFile = pkgs.writeText "mailman-raw.cfg" mailmanCfg;
 
   mailmanHyperkittyCfg = pkgs.writeText "mailman-hyperkitty.cfg" ''
     [general]
@@ -72,6 +78,9 @@ in {
       stored in the world-readable Nix store.  To continue using
       Hyperkitty, you must set services.mailman.hyperkitty.enable = true.
     '')
+    (mkRemovedOptionModule [ "services" "mailman" "package" ] ''
+      Didn't have an effect for several years.
+    '')
   ];
 
   options = {
@@ -81,22 +90,122 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable Mailman on this host. Requires an active MTA on the host (e.g. Postfix).";
+        description = lib.mdDoc "Enable Mailman on this host. Requires an active MTA on the host (e.g. Postfix).";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mailman;
-        defaultText = literalExpression "pkgs.mailman";
-        example = literalExpression "pkgs.mailman.override { archivers = []; }";
-        description = "Mailman package to use";
+      ldap = {
+        enable = mkEnableOption (lib.mdDoc "LDAP auth");
+        serverUri = mkOption {
+          type = types.str;
+          example = "ldaps://ldap.host";
+          description = lib.mdDoc ''
+            LDAP host to connect against.
+          '';
+        };
+        bindDn = mkOption {
+          type = types.str;
+          example = "cn=root,dc=nixos,dc=org";
+          description = lib.mdDoc ''
+            Service account to bind against.
+          '';
+        };
+        bindPasswordFile = mkOption {
+          type = types.str;
+          example = "/run/secrets/ldap-bind";
+          description = lib.mdDoc ''
+            Path to the file containing the bind password of the service account
+            defined by [](#opt-services.mailman.ldap.bindDn).
+          '';
+        };
+        superUserGroup = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "cn=admin,ou=groups,dc=nixos,dc=org";
+          description = lib.mdDoc ''
+            Group where a user must be a member of to gain superuser rights.
+          '';
+        };
+        userSearch = {
+          query = mkOption {
+            type = types.str;
+            example = "(&(objectClass=inetOrgPerson)(|(uid=%(user)s)(mail=%(user)s)))";
+            description = lib.mdDoc ''
+              Query to find a user in the LDAP database.
+            '';
+          };
+          ou = mkOption {
+            type = types.str;
+            example = "ou=users,dc=nixos,dc=org";
+            description = lib.mdDoc ''
+              Organizational unit to look up a user.
+            '';
+          };
+        };
+        groupSearch = {
+          type = mkOption {
+            type = types.enum [
+              "posixGroup" "groupOfNames" "memberDNGroup" "nestedMemberDNGroup" "nestedGroupOfNames"
+              "groupOfUniqueNames" "nestedGroupOfUniqueNames" "activeDirectoryGroup" "nestedActiveDirectoryGroup"
+              "organizationalRoleGroup" "nestedOrganizationalRoleGroup"
+            ];
+            default = "posixGroup";
+            apply = v: "${toUpper (substring 0 1 v)}${substring 1 (stringLength v) v}Type";
+            description = lib.mdDoc ''
+              Type of group to perform a group search against.
+            '';
+          };
+          query = mkOption {
+            type = types.str;
+            example = "(objectClass=groupOfNames)";
+            description = lib.mdDoc ''
+              Query to find a group associated to a user in the LDAP database.
+            '';
+          };
+          ou = mkOption {
+            type = types.str;
+            example = "ou=groups,dc=nixos,dc=org";
+            description = lib.mdDoc ''
+              Organizational unit to look up a group.
+            '';
+          };
+        };
+        attrMap = {
+          username = mkOption {
+            default = "uid";
+            type = types.str;
+            description = lib.mdDoc ''
+              LDAP-attribute that corresponds to the `username`-attribute in mailman.
+            '';
+          };
+          firstName = mkOption {
+            default = "givenName";
+            type = types.str;
+            description = lib.mdDoc ''
+              LDAP-attribute that corresponds to the `firstName`-attribute in mailman.
+            '';
+          };
+          lastName = mkOption {
+            default = "sn";
+            type = types.str;
+            description = lib.mdDoc ''
+              LDAP-attribute that corresponds to the `lastName`-attribute in mailman.
+            '';
+          };
+          email = mkOption {
+            default = "mail";
+            type = types.str;
+            description = lib.mdDoc ''
+              LDAP-attribute that corresponds to the `email`-attribute in mailman.
+            '';
+          };
+        };
       };
 
       enablePostfix = mkOption {
         type = types.bool;
         default = true;
         example = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Postfix integration. Requires an active Postfix installation.
 
           If you want to use another MTA, set this option to false and configure
@@ -109,7 +218,7 @@ in {
       siteOwner = mkOption {
         type = types.str;
         example = "postmaster@example.org";
-        description = ''
+        description = lib.mdDoc ''
           Certain messages that must be delivered to a human, but which can't
           be delivered to a list owner (e.g. a bounce from a list owner), will
           be sent to this address. It should point to a human.
@@ -119,7 +228,7 @@ in {
       webHosts = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           The list of hostnames and/or IP addresses from which the Mailman Web
           UI will accept requests. By default, "localhost" and "127.0.0.1" are
           enabled. All additional names under which your web server accepts
@@ -131,7 +240,7 @@ in {
       webUser = mkOption {
         type = types.str;
         default = "mailman-web";
-        description = ''
+        description = lib.mdDoc ''
           User to run mailman-web as
         '';
       };
@@ -139,34 +248,51 @@ in {
       webSettings = mkOption {
         type = types.attrs;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Overrides for the default mailman-web Django settings.
         '';
       };
 
+      restApiPassFile = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          Path to the file containing the value for `MAILMAN_REST_API_PASS`.
+        '';
+      };
+
       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 = "/";
+          example = lib.literalExpression "/lists";
+          type = types.str;
+          description = lib.mdDoc ''
+            Path to mount the mailman-web django application on.
+          '';
+        };
       };
 
       extraPythonPackages = mkOption {
-        description = "Packages to add to the python environment used by mailman and mailman-web";
+        description = lib.mdDoc "Packages to add to the python environment used by mailman and mailman-web";
         type = types.listOf types.package;
         default = [];
       };
 
       settings = mkOption {
-        description = "Settings for mailman.cfg";
+        description = lib.mdDoc "Settings for mailman.cfg";
         type = types.attrsOf (types.attrsOf types.str);
         default = {};
       };
 
       hyperkitty = {
-        enable = mkEnableOption "the Hyperkitty archiver for Mailman";
+        enable = mkEnableOption (lib.mdDoc "the Hyperkitty archiver for Mailman");
 
         baseUrl = mkOption {
           type = types.str;
           default = "http://localhost:18507/archives/";
-          description = ''
+          description = lib.mdDoc ''
             Where can Mailman connect to Hyperkitty's internal API, preferably on
             localhost?
           '';
@@ -185,7 +311,7 @@ in {
       mailman.layout = "fhs";
 
       "paths.fhs" = {
-        bin_dir = "${pkgs.python3Packages.mailman}/bin";
+        bin_dir = "${pkgs.mailmanPackages.mailman}/bin";
         var_dir = "/var/lib/mailman";
         queue_dir = "$var_dir/queue";
         template_dir = "$var_dir/templates";
@@ -260,8 +386,6 @@ in {
     };
     users.groups.mailman = {};
 
-    environment.etc."mailman.cfg".text = mailmanCfg;
-
     environment.etc."mailman3/settings.py".text = ''
       import os
 
@@ -279,14 +403,47 @@ in {
 
       with open('/var/lib/mailman-web/settings_local.json') as f:
           globals().update(json.load(f))
+
+      ${optionalString (cfg.restApiPassFile != null) ''
+        with open('${cfg.restApiPassFile}') as f:
+            MAILMAN_REST_API_PASS = f.read().rstrip('\n')
+      ''}
+
+      ${optionalString (cfg.ldap.enable) ''
+        import ldap
+        from django_auth_ldap.config import LDAPSearch, ${cfg.ldap.groupSearch.type}
+        AUTH_LDAP_SERVER_URI = "${cfg.ldap.serverUri}"
+        AUTH_LDAP_BIND_DN = "${cfg.ldap.bindDn}"
+        with open("${cfg.ldap.bindPasswordFile}") as f:
+            AUTH_LDAP_BIND_PASSWORD = f.read().rstrip('\n')
+        AUTH_LDAP_USER_SEARCH = LDAPSearch("${cfg.ldap.userSearch.ou}",
+            ldap.SCOPE_SUBTREE, "${cfg.ldap.userSearch.query}")
+        AUTH_LDAP_GROUP_TYPE = ${cfg.ldap.groupSearch.type}()
+        AUTH_LDAP_GROUP_SEARCH = LDAPSearch("${cfg.ldap.groupSearch.ou}",
+            ldap.SCOPE_SUBTREE, "${cfg.ldap.groupSearch.query}")
+        AUTH_LDAP_USER_ATTR_MAP = {
+          ${concatStrings (flip mapAttrsToList cfg.ldap.attrMap (key: value: ''
+            "${key}": "${value}",
+          ''))}
+        }
+        ${optionalString (cfg.ldap.superUserGroup != null) ''
+          AUTH_LDAP_USER_FLAGS_BY_GROUP = {
+            "is_superuser": "${cfg.ldap.superUserGroup}"
+          }
+        ''}
+        AUTHENTICATION_BACKENDS = (
+            "django_auth_ldap.backend.LDAPBackend",
+            "django.contrib.auth.backends.ModelBackend"
+        )
+      ''}
     '';
 
     services.nginx = mkIf (cfg.serve.enable && cfg.webHosts != []) {
       enable = mkDefault true;
       virtualHosts = lib.genAttrs cfg.webHosts (webHost: {
         locations = {
-          "/".extraConfig = "uwsgi_pass unix:/run/mailman-web.socket;";
-          "/static/".alias = webSettings.STATIC_ROOT + "/";
+          ${cfg.serve.virtualRoot}.extraConfig = "uwsgi_pass unix:/run/mailman-web.socket;";
+          "${removeSuffix "/" cfg.serve.virtualRoot}/static/".alias = webSettings.STATIC_ROOT + "/";
         };
       });
     };
@@ -295,9 +452,12 @@ in {
       name = "mailman-tools";
       # We don't want to pollute the system PATH with a python
       # interpreter etc. so let's pick only the stuff we actually
-      # want from pythonEnv
+      # want from {web,mailman}Env
       pathsToLink = ["/bin"];
-      paths = [pythonEnv];
+      paths = [ mailmanEnv webEnv ];
+      # Only mailman-related stuff is installed, the rest is removed
+      # in `postBuild`.
+      ignoreCollisions = true;
       postBuild = ''
         find $out/bin/ -mindepth 1 -not -name "mailman*" -delete
       '';
@@ -320,12 +480,14 @@ in {
         description = "GNU Mailman Master Process";
         before = lib.optional cfg.enablePostfix "postfix.service";
         after = [ "network.target" ]
-          ++ lib.optional cfg.enablePostfix "postfix-setup.service";
-        restartTriggers = [ config.environment.etc."mailman.cfg".source ];
+          ++ lib.optional cfg.enablePostfix "postfix-setup.service"
+          ++ lib.optional withPostgresql "postgresql.service";
+        restartTriggers = [ mailmanCfgFile ];
+        requires = optional withPostgresql "postgresql.service";
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
-          ExecStart = "${pythonEnv}/bin/mailman start";
-          ExecStop = "${pythonEnv}/bin/mailman stop";
+          ExecStart = "${mailmanEnv}/bin/mailman start";
+          ExecStop = "${mailmanEnv}/bin/mailman stop";
           User = "mailman";
           Group = "mailman";
           Type = "forking";
@@ -340,8 +502,18 @@ in {
         before = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
         requiredBy = [ "mailman.service" "mailman-web-setup.service" "mailman-uwsgi.service" "hyperkitty.service" ];
         path = with pkgs; [ jq ];
+        after = optional withPostgresql "postgresql.service";
+        requires = optional withPostgresql "postgresql.service";
         serviceConfig.Type = "oneshot";
         script = ''
+          install -m0750 -o mailman -g mailman ${mailmanCfgFile} /etc/mailman.cfg
+          ${optionalString (cfg.restApiPassFile != null) ''
+            ${pkgs.replace-secret}/bin/replace-secret \
+              '#NIXOS_MAILMAN_REST_API_PASS_SECRET#' \
+              ${cfg.restApiPassFile} \
+              /etc/mailman.cfg
+          ''}
+
           mailmanDir=/var/lib/mailman
           mailmanWebDir=/var/lib/mailman-web
 
@@ -381,9 +553,9 @@ in {
         restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
         script = ''
           [[ -e "${webSettings.STATIC_ROOT}" ]] && find "${webSettings.STATIC_ROOT}/" -mindepth 1 -delete
-          ${pythonEnv}/bin/mailman-web migrate
-          ${pythonEnv}/bin/mailman-web collectstatic
-          ${pythonEnv}/bin/mailman-web compress
+          ${webEnv}/bin/mailman-web migrate
+          ${webEnv}/bin/mailman-web collectstatic
+          ${webEnv}/bin/mailman-web compress
         '';
         serviceConfig = {
           User = cfg.webUser;
@@ -397,14 +569,17 @@ in {
         uwsgiConfig.uwsgi = {
           type = "normal";
           plugins = ["python3"];
-          home = pythonEnv;
-          module = "mailman_web.wsgi";
+          home = webEnv;
+          manage-script-name = true;
+          mount = "${cfg.serve.virtualRoot}=mailman_web.wsgi:application";
           http = "127.0.0.1:18507";
         };
         uwsgiConfigFile = pkgs.writeText "uwsgi-mailman.json" (builtins.toJSON uwsgiConfig);
       in {
         wantedBy = ["multi-user.target"];
-        requires = ["mailman-uwsgi.socket" "mailman-web-setup.service"];
+        after = optional withPostgresql "postgresql.service";
+        requires = ["mailman-uwsgi.socket" "mailman-web-setup.service"]
+          ++ optional withPostgresql "postgresql.service";
         restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
         serviceConfig = {
           # Since the mailman-web settings.py obstinately creates a logs
@@ -420,9 +595,9 @@ in {
       mailman-daily = {
         description = "Trigger daily Mailman events";
         startAt = "daily";
-        restartTriggers = [ config.environment.etc."mailman.cfg".source ];
+        restartTriggers = [ mailmanCfgFile ];
         serviceConfig = {
-          ExecStart = "${pythonEnv}/bin/mailman digests --send";
+          ExecStart = "${mailmanEnv}/bin/mailman digests --send";
           User = "mailman";
           Group = "mailman";
         };
@@ -434,7 +609,7 @@ in {
         restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
         wantedBy = [ "mailman.service" "multi-user.target" ];
         serviceConfig = {
-          ExecStart = "${pythonEnv}/bin/mailman-web qcluster";
+          ExecStart = "${webEnv}/bin/mailman-web qcluster";
           User = cfg.webUser;
           Group = "mailman";
           WorkingDirectory = "/var/lib/mailman-web";
@@ -453,7 +628,7 @@ in {
         inherit startAt;
         restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
         serviceConfig = {
-          ExecStart = "${pythonEnv}/bin/mailman-web runjobs ${name}";
+          ExecStart = "${webEnv}/bin/mailman-web runjobs ${name}";
           User = cfg.webUser;
           Group = "mailman";
           WorkingDirectory = "/var/lib/mailman-web";
@@ -462,7 +637,7 @@ in {
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ lheckemann qyliss ];
+    maintainers = with lib.maintainers; [ lheckemann qyliss ma27 ];
     doc = ./mailman.xml;
   };
 
diff --git a/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix
index fd74f2dc5f07..642f8b20fe35 100644
--- a/nixos/modules/services/mail/mlmmj.nix
+++ b/nixos/modules/services/mail/mlmmj.nix
@@ -56,40 +56,39 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable mlmmj";
+        description = lib.mdDoc "Enable mlmmj";
       };
 
       user = mkOption {
         type = types.str;
         default = "mlmmj";
-        description = "mailinglist local user";
+        description = lib.mdDoc "mailinglist local user";
       };
 
       group = mkOption {
         type = types.str;
         default = "mlmmj";
-        description = "mailinglist local group";
+        description = lib.mdDoc "mailinglist local group";
       };
 
       listDomain = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Set the mailing list domain";
+        description = lib.mdDoc "Set the mailing list domain";
       };
 
       mailLists = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "The collection of hosted maillists";
+        description = lib.mdDoc "The collection of hosted maillists";
       };
 
       maintInterval = mkOption {
         type = types.str;
         default = "20min";
-        description = ''
+        description = lib.mdDoc ''
           Time interval between mlmmj-maintd runs, see
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry> for format information.
+          {manpage}`systemd.time(7)` for format information.
         '';
       };
 
diff --git a/nixos/modules/services/mail/nullmailer.nix b/nixos/modules/services/mail/nullmailer.nix
index f9c345669978..7c72229efb24 100644
--- a/nixos/modules/services/mail/nullmailer.nix
+++ b/nixos/modules/services/mail/nullmailer.nix
@@ -10,13 +10,13 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable nullmailer daemon.";
+        description = lib.mdDoc "Whether to enable nullmailer daemon.";
       };
 
       user = mkOption {
         type = types.str;
         default = "nullmailer";
-        description = ''
+        description = lib.mdDoc ''
           User to use to run nullmailer-send.
         '';
       };
@@ -24,7 +24,7 @@ with lib;
       group = mkOption {
         type = types.str;
         default = "nullmailer";
-        description = ''
+        description = lib.mdDoc ''
           Group to use to run nullmailer-send.
         '';
       };
@@ -32,17 +32,17 @@ with lib;
       setSendmail = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to set the system sendmail to nullmailer's.";
+        description = lib.mdDoc "Whether to set the system sendmail to nullmailer's.";
       };
 
       remotesFile = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
-          Path to the <code>remotes</code> control file. This file contains a
+        description = lib.mdDoc ''
+          Path to the `remotes` control file. This file contains a
           list of remote servers to which to send each message.
 
-          See <code>man 8 nullmailer-send</code> for syntax and available
+          See `man 8 nullmailer-send` for syntax and available
           options.
         '';
       };
@@ -51,7 +51,7 @@ with lib;
         adminaddr = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             If set, all recipients to users at either "localhost" (the literal string)
             or the canonical host name (from the me control attribute) are remapped to this address.
             This is provided to allow local daemons to be able to send email to
@@ -64,7 +64,7 @@ with lib;
         allmailfrom = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             If set, content will override the envelope sender on all messages.
           '';
         };
@@ -72,7 +72,7 @@ with lib;
         defaultdomain = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
              The content of this attribute is appended to any host name that
              does not contain a period (except localhost), including defaulthost
              and idhost. Defaults to the value of the me attribute, if it exists,
@@ -83,7 +83,7 @@ with lib;
         defaulthost = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
              The content of this attribute is appended to any address that
              is missing a host name. Defaults to the value of the me control
              attribute, if it exists, otherwise the literal name defaulthost.
@@ -93,7 +93,7 @@ with lib;
         doublebounceto = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             If the original sender was empty (the original message was a
             delivery status or disposition notification), the double bounce
             is sent to the address in this attribute.
@@ -103,7 +103,7 @@ with lib;
         helohost = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Sets  the  environment variable $HELOHOST which is used by the
             SMTP protocol module to set the parameter given to the HELO command.
             Defaults to the value of the me configuration attribute.
@@ -113,7 +113,7 @@ with lib;
         idhost = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The content of this attribute is used when building the message-id
             string for the message. Defaults to the canonicalized value of defaulthost.
           '';
@@ -122,7 +122,7 @@ with lib;
         maxpause = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
              The maximum time to pause between successive queue runs, in seconds.
              Defaults to 24 hours (86400).
           '';
@@ -131,7 +131,7 @@ with lib;
         me = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
              The fully-qualifiled host name of the computer running nullmailer.
              Defaults to the literal name me.
           '';
@@ -140,7 +140,7 @@ with lib;
         pausetime = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The minimum time to pause between successive queue runs when there
             are messages in the queue, in seconds. Defaults to 1 minute (60).
             Each time this timeout is reached, the timeout is doubled to a
@@ -153,24 +153,24 @@ with lib;
         remotes = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             A list of remote servers to which to send each message. Each line
             contains a remote host name or address followed by an optional
             protocol string, separated by white space.
 
-            See <code>man 8 nullmailer-send</code> for syntax and available
+            See `man 8 nullmailer-send` for syntax and available
             options.
 
             WARNING: This is stored world-readable in the nix store. If you need
             to specify any secret credentials here, consider using the
-            <code>remotesFile</code> option instead.
+            `remotesFile` option instead.
           '';
         };
 
         sendtimeout = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The  time to wait for a remote module listed above to complete sending
             a message before killing it and trying again, in seconds.
             Defaults to 1 hour (3600).  If this is set to 0, nullmailer-send
@@ -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/nixos/modules/services/mail/offlineimap.nix b/nixos/modules/services/mail/offlineimap.nix
index 451477581190..64fa09e83612 100644
--- a/nixos/modules/services/mail/offlineimap.nix
+++ b/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`.
       '';
     };
 
@@ -26,26 +26,26 @@ in {
       type = types.package;
       default = pkgs.offlineimap;
       defaultText = literalExpression "pkgs.offlineimap";
-      description = "Offlineimap derivation to use.";
+      description = lib.mdDoc "Offlineimap derivation to use.";
     };
 
     path = mkOption {
       type = types.listOf types.path;
       default = [];
       example = literalExpression "[ pkgs.pass pkgs.bash pkgs.notmuch ]";
-      description = "List of derivations to put in Offlineimap's path.";
+      description = lib.mdDoc "List of derivations to put in Offlineimap's path.";
     };
 
     onCalendar = mkOption {
       type = types.str;
       default = "*:0/3"; # every 3 minutes
-      description = "How often is offlineimap started. Default is '*:0/3' meaning every 3 minutes. See systemd.time(7) for more information about the format.";
+      description = lib.mdDoc "How often is offlineimap started. Default is '*:0/3' meaning every 3 minutes. See systemd.time(7) for more information about the format.";
     };
 
     timeoutStartSec = mkOption {
       type = types.str;
       default = "120sec"; # Kill if still alive after 2 minutes
-      description = "How long waiting for offlineimap before killing it. Default is '120sec' meaning every 2 minutes. See systemd.time(7) for more information about the format.";
+      description = lib.mdDoc "How long waiting for offlineimap before killing it. Default is '120sec' meaning every 2 minutes. See systemd.time(7) for more information about the format.";
     };
   };
   config = mkIf (cfg.enable || cfg.install) {
diff --git a/nixos/modules/services/mail/opendkim.nix b/nixos/modules/services/mail/opendkim.nix
index f1ffc5d3aeef..a377fccc7bd2 100644
--- a/nixos/modules/services/mail/opendkim.nix
+++ b/nixos/modules/services/mail/opendkim.nix
@@ -31,25 +31,25 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the OpenDKIM sender authentication system.";
+        description = lib.mdDoc "Whether to enable the OpenDKIM sender authentication system.";
       };
 
       socket = mkOption {
         type = types.str;
         default = defaultSock;
-        description = "Socket which is used for communication with OpenDKIM.";
+        description = lib.mdDoc "Socket which is used for communication with OpenDKIM.";
       };
 
       user = mkOption {
         type = types.str;
         default = "opendkim";
-        description = "User for the daemon.";
+        description = lib.mdDoc "User for the daemon.";
       };
 
       group = mkOption {
         type = types.str;
         default = "opendkim";
-        description = "Group for the daemon.";
+        description = lib.mdDoc "Group for the daemon.";
       };
 
       domains = mkOption {
@@ -57,15 +57,15 @@ in {
         default = "csl:${config.networking.hostName}";
         defaultText = literalExpression ''"csl:''${config.networking.hostName}"'';
         example = "csl:example.com,mydomain.net";
-        description = ''
-          Local domains set (see <literal>opendkim(8)</literal> for more information on datasets).
+        description = lib.mdDoc ''
+          Local domains set (see `opendkim(8)` for more information on datasets).
           Messages from them are signed, not verified.
         '';
       };
 
       keyPath = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path that opendkim should put its generated private keys into.
           The DNS settings will be found in this directory with the name selector.txt.
         '';
@@ -74,13 +74,13 @@ in {
 
       selector = mkOption {
         type = types.str;
-        description = "Selector to use when signing.";
+        description = lib.mdDoc "Selector to use when signing.";
       };
 
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "Additional opendkim configuration.";
+        description = lib.mdDoc "Additional opendkim configuration.";
       };
 
     };
diff --git a/nixos/modules/services/mail/opensmtpd.nix b/nixos/modules/services/mail/opensmtpd.nix
index e7632be28045..6ad3386d2d4e 100644
--- a/nixos/modules/services/mail/opensmtpd.nix
+++ b/nixos/modules/services/mail/opensmtpd.nix
@@ -28,27 +28,27 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the OpenSMTPD server.";
+        description = lib.mdDoc "Whether to enable the OpenSMTPD server.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.opensmtpd;
         defaultText = literalExpression "pkgs.opensmtpd";
-        description = "The OpenSMTPD package to use.";
+        description = lib.mdDoc "The OpenSMTPD package to use.";
       };
 
       setSendmail = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to set the system sendmail to OpenSMTPD's.";
+        description = lib.mdDoc "Whether to set the system sendmail to OpenSMTPD's.";
       };
 
       extraServerArgs = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "-v" "-P mta" ];
-        description = ''
+        description = lib.mdDoc ''
           Extra command line arguments provided when the smtpd process
           is started.
         '';
@@ -60,7 +60,7 @@ in {
           listen on lo
           accept for any deliver to lmtp localhost:24
         '';
-        description = ''
+        description = lib.mdDoc ''
           The contents of the smtpd.conf configuration file. See the
           OpenSMTPD documentation for syntax information.
         '';
@@ -69,7 +69,7 @@ in {
       procPackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Packages to search for filters, tables, queues, and schedulers.
 
           Add OpenSMTPD-extras here if you want to use the filters, etc. from
diff --git a/nixos/modules/services/mail/pfix-srsd.nix b/nixos/modules/services/mail/pfix-srsd.nix
index e3dbf2a014f0..237f36945e4b 100644
--- a/nixos/modules/services/mail/pfix-srsd.nix
+++ b/nixos/modules/services/mail/pfix-srsd.nix
@@ -12,20 +12,20 @@ with lib;
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to run the postfix sender rewriting scheme daemon.";
+        description = lib.mdDoc "Whether to run the postfix sender rewriting scheme daemon.";
       };
 
       domain = mkOption {
-        description = "The domain for which to enable srs";
+        description = lib.mdDoc "The domain for which to enable srs";
         type = types.str;
         example = "example.com";
       };
 
       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/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index da14b6eef7ed..d01734d61e87 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -45,7 +45,7 @@ let
         type = types.str;
         default = name;
         example = "smtp";
-        description = ''
+        description = lib.mdDoc ''
           The name of the service to run. Defaults to the attribute set key.
         '';
       };
@@ -54,41 +54,41 @@ let
         type = types.enum [ "inet" "unix" "unix-dgram" "fifo" "pass" ];
         default = "unix";
         example = "inet";
-        description = "The type of the service";
+        description = lib.mdDoc "The type of the service";
       };
 
       private = mkOption {
         type = types.bool;
         example = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether the service's sockets and storage directory is restricted to
-          be only available via the mail system. If <literal>null</literal> is
-          given it uses the postfix default <literal>true</literal>.
+          be only available via the mail system. If `null` is
+          given it uses the postfix default `true`.
         '';
       };
 
       privileged = mkOption {
         type = types.bool;
         example = true;
-        description = "";
+        description = lib.mdDoc "";
       };
 
       chroot = mkOption {
         type = types.bool;
         example = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the service is chrooted to have only access to the
-          <option>services.postfix.queueDir</option> and the closure of
-          store paths specified by the <option>program</option> option.
+          {option}`services.postfix.queueDir` and the closure of
+          store paths specified by the {option}`program` option.
         '';
       };
 
       wakeup = mkOption {
         type = types.int;
         example = 60;
-        description = ''
+        description = lib.mdDoc ''
           Automatically wake up the service after the specified number of
-          seconds. If <literal>0</literal> is given, never wake the service
+          seconds. If `0` is given, never wake the service
           up.
         '';
       };
@@ -96,22 +96,22 @@ let
       wakeupUnusedComponent = mkOption {
         type = types.bool;
         example = false;
-        description = ''
-          If set to <literal>false</literal> the component will only be woken
+        description = lib.mdDoc ''
+          If set to `false` the component will only be woken
           up if it is used. This is equivalent to postfix' notion of adding a
           question mark behind the wakeup time in
-          <filename>master.cf</filename>
+          {file}`master.cf`
         '';
       };
 
       maxproc = mkOption {
         type = types.int;
         example = 1;
-        description = ''
+        description = lib.mdDoc ''
           The maximum number of processes to spawn for this service. If the
-          value is <literal>0</literal> it doesn't have any limit. If
-          <literal>null</literal> is given it uses the postfix default of
-          <literal>100</literal>.
+          value is `0` it doesn't have any limit. If
+          `null` is given it uses the postfix default of
+          `100`.
         '';
       };
 
@@ -119,9 +119,9 @@ let
         type = types.str;
         default = name;
         example = "smtpd";
-        description = ''
+        description = lib.mdDoc ''
           A program name specifying a Postfix service/daemon process.
-          By default it's the attribute <option>name</option>.
+          By default it's the attribute {option}`name`.
         '';
       };
 
@@ -129,8 +129,8 @@ let
         type = types.listOf types.str;
         default = [];
         example = [ "-o" "smtp_helo_timeout=5" ];
-        description = ''
-          Arguments to pass to the <option>command</option>. There is no shell
+        description = lib.mdDoc ''
+          Arguments to pass to the {option}`command`. There is no shell
           processing involved and shell syntax is passed verbatim to the
           process.
         '';
@@ -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`.
         '';
       };
     };
@@ -221,25 +221,25 @@ let
         type = types.str;
         default = "/^.*/";
         example = "/^X-Mailer:/";
-        description = "A regexp pattern matching the header";
+        description = lib.mdDoc "A regexp pattern matching the header";
       };
       action = mkOption {
         type = types.str;
         default = "DUNNO";
         example = "BCC mail@example.com";
-        description = "The action to be executed when the pattern is matched";
+        description = lib.mdDoc "The action to be executed when the pattern is matched";
       };
     };
   };
 
   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 = if cfg.aliasMapType == "hash" then ":" else ""; 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
   ;
@@ -267,25 +267,25 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to run the Postfix mail server.";
+        description = lib.mdDoc "Whether to run the Postfix mail server.";
       };
 
       enableSmtp = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable smtp in master.cf.";
+        description = lib.mdDoc "Whether to enable smtp in master.cf.";
       };
 
       enableSubmission = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable smtp submission.";
+        description = lib.mdDoc "Whether to enable smtp submission.";
       };
 
       enableSubmissions = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable smtp submission via smtps.
 
           According to RFC 8314 this should be preferred
@@ -308,7 +308,7 @@ in
           smtpd_client_restrictions = "permit_sasl_authenticated,reject";
           milter_macro_daemon_name = "ORIGINATING";
         };
-        description = "Options for the submission config in master.cf";
+        description = lib.mdDoc "Options for the submission config in master.cf";
       };
 
       submissionsOptions = mkOption {
@@ -324,7 +324,7 @@ in
           smtpd_client_restrictions = "permit_sasl_authenticated,reject";
           milter_macro_daemon_name = "ORIGINATING";
         };
-        description = ''
+        description = lib.mdDoc ''
           Options for the submission config via smtps in master.cf.
 
           smtpd_tls_security_level will be set to encrypt, if it is missing
@@ -337,155 +337,155 @@ in
       setSendmail = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to set the system sendmail to postfix's.";
+        description = lib.mdDoc "Whether to set the system sendmail to postfix's.";
       };
 
       user = mkOption {
         type = types.str;
         default = "postfix";
-        description = "What to call the Postfix user (must be used only for postfix).";
+        description = lib.mdDoc "What to call the Postfix user (must be used only for postfix).";
       };
 
       group = mkOption {
         type = types.str;
         default = "postfix";
-        description = "What to call the Postfix group (must be used only for postfix).";
+        description = lib.mdDoc "What to call the Postfix group (must be used only for postfix).";
       };
 
       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 {
         type = with types; enum [ "hash" "regexp" "pcre" ];
         default = "hash";
         example = "regexp";
-        description = "The format the alias map should have. Use regexp if you want to use regular expressions.";
+        description = lib.mdDoc "The format the alias map should have. Use regexp if you want to use regular expressions.";
       };
 
       config = mkOption {
         type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
-        description = ''
+        description = lib.mdDoc ''
           The main.cf configuration file as key value set.
         '';
         example = {
@@ -497,16 +497,16 @@ 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 {
         type = types.str;
         default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
         defaultText = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
-        description = ''
+        description = lib.mdDoc ''
           File containing trusted certification authorities (CA) to verify certificates of mailservers contacted for mail delivery. This basically sets smtp_tls_CAfile and enables opportunistic tls. Defaults to NixOS trusted certification authorities.
         '';
       };
@@ -514,56 +514,55 @@ in
       sslCert = mkOption {
         type = types.str;
         default = "";
-        description = "SSL certificate to use.";
+        description = lib.mdDoc "SSL certificate to use.";
       };
 
       sslKey = mkOption {
         type = types.str;
         default = "";
-        description = "SSL key to use.";
+        description = lib.mdDoc "SSL key to use.";
       };
 
       recipientDelimiter = mkOption {
         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 {
         type = types.lines;
         default = "";
-        description = ''
-          Entries for the <citerefentry><refentrytitle>canonical</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> table.
+        description = lib.mdDoc ''
+          Entries for the {manpage}`canonical(5)` table.
         '';
       };
 
       virtual = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Entries for the virtual alias map, cf. man-page virtual(5).
-        ";
+        '';
       };
 
       virtualMapType = mkOption {
         type = types.enum ["hash" "regexp" "pcre"];
         default = "hash";
-        description = ''
-          What type of virtual alias map file to use. Use <literal>"regexp"</literal> for regular expressions.
+        description = lib.mdDoc ''
+          What type of virtual alias map file to use. Use `"regexp"` for regular expressions.
         '';
       };
 
       localRecipients = mkOption {
         type = with types; nullOr (listOf str);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           List of accepted local users. Specify a bare username, an
-          <literal>"@domain.tld"</literal> wild-card, or a complete
-          <literal>"user@domain.tld"</literal> address. If set, these names end
+          `"@domain.tld"` wild-card, or a complete
+          `"user@domain.tld"` address. If set, these names end
           up in the local recipient map -- see the local(8) man-page -- and
           effectively replace the system user database lookup that's otherwise
           used by default.
@@ -573,21 +572,21 @@ in
       transport = mkOption {
         default = "";
         type = types.lines;
-        description = "
+        description = lib.mdDoc ''
           Entries for the transport map, cf. man-page transport(8).
-        ";
+        '';
       };
 
       dnsBlacklists = mkOption {
         default = [];
         type = with types; listOf str;
-        description = "dns blacklist servers to use with smtpd_client_restrictions";
+        description = lib.mdDoc "dns blacklist servers to use with smtpd_client_restrictions";
       };
 
       dnsBlacklistOverrides = mkOption {
         default = "";
         type = types.lines;
-        description = "contents of check_client_access for overriding dnsBlacklists";
+        description = lib.mdDoc "contents of check_client_access for overriding dnsBlacklists";
       };
 
       masterConfig = mkOption {
@@ -599,10 +598,10 @@ in
               args = [ "-o" "smtpd_tls_security_level=encrypt" ];
             };
           };
-        description = ''
+        description = lib.mdDoc ''
           An attribute set of service options, which correspond to the service
           definitions usually done within the Postfix
-          <filename>master.cf</filename> file.
+          {file}`master.cf` file.
         '';
       };
 
@@ -610,46 +609,46 @@ in
         type = types.lines;
         default = "";
         example = "submission inet n - n - - smtpd";
-        description = "Extra lines to append to the generated master.cf file.";
+        description = lib.mdDoc "Extra lines to append to the generated master.cf file.";
       };
 
       enableHeaderChecks = mkOption {
         type = types.bool;
         default = false;
         example = true;
-        description = "Whether to enable postfix header checks";
+        description = lib.mdDoc "Whether to enable postfix header checks";
       };
 
       headerChecks = mkOption {
         type = types.listOf (types.submodule headerCheckOptions);
         default = [];
         example = [ { pattern = "/^X-Spam-Flag:/"; action = "REDIRECT spam@example.com"; } ];
-        description = "Postfix header checks.";
+        description = lib.mdDoc "Postfix header checks.";
       };
 
       extraHeaderChecks = mkOption {
         type = types.lines;
         default = "";
         example = "/^X-Spam-Flag:/ REDIRECT spam@example.com";
-        description = "Extra lines to /etc/postfix/header_checks file.";
+        description = lib.mdDoc "Extra lines to /etc/postfix/header_checks file.";
       };
 
       aliasFiles = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
+        description = lib.mdDoc "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
       };
 
       mapFiles = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
+        description = lib.mdDoc "Maps to be compiled and placed into /var/lib/postfix/conf.";
       };
 
       useSrs = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable sender rewriting scheme";
+        description = lib.mdDoc "Whether to enable sender rewriting scheme";
       };
 
     };
@@ -725,6 +724,7 @@ in
 
       systemd.services.postfix-setup =
         { description = "Setup for Postfix mail server";
+          serviceConfig.RemainAfterExit = true;
           serviceConfig.Type = "oneshot";
           script = ''
             # Backwards compatibility
diff --git a/nixos/modules/services/mail/postfixadmin.nix b/nixos/modules/services/mail/postfixadmin.nix
index a0846ad52902..b86428770cb2 100644
--- a/nixos/modules/services/mail/postfixadmin.nix
+++ b/nixos/modules/services/mail/postfixadmin.nix
@@ -13,25 +13,25 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable postfixadmin.
 
         Also enables nginx virtual host management.
-        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
-        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+        Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
+        See [](#opt-services.nginx.virtualHosts) for further information.
       '';
     };
 
     hostName = mkOption {
       type = types.str;
       example = "postfixadmin.example.com";
-      description = "Hostname to use for the nginx vhost";
+      description = lib.mdDoc "Hostname to use for the nginx vhost";
     };
 
     adminEmail = mkOption {
       type = types.str;
       example = "postmaster@example.com";
-      description = ''
+      description = lib.mdDoc ''
         Defines the Site Admin's email address.
         This will be used to send emails from to create mailboxes and
         from Send Email / Broadcast message pages.
@@ -40,9 +40,9 @@ in
 
     setupPasswordFile = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Password file for the admin.
-        Generate with <literal>php -r "echo password_hash('some password here', PASSWORD_DEFAULT);"</literal>
+        Generate with `php -r "echo password_hash('some password here', PASSWORD_DEFAULT);"`
       '';
     };
 
@@ -50,36 +50,36 @@ in
       username = mkOption {
         type = types.str;
         default = "postfixadmin";
-        description = ''
+        description = lib.mdDoc ''
           Username for the postgresql connection.
-          If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well.
+          If `database.host` is set to `localhost`, a unix user and group of the same name will be created as well.
         '';
       };
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           Host of the postgresql server. If this is not set to
-          <literal>localhost</literal>, you have to create the
+          `localhost`, you have to create the
           postgresql user and database yourself, with appropriate
           permissions.
         '';
       };
       passwordFile = mkOption {
         type = types.path;
-        description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>.";
+        description = lib.mdDoc "Password file for the postgresql connection. Must be readable by user `nginx`.";
       };
       dbname = mkOption {
         type = types.str;
         default = "postfixadmin";
-        description = "Name of the postgresql database";
+        description = lib.mdDoc "Name of the postgresql database";
       };
     };
 
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = "Extra configuration for the postfixadmin instance, see postfixadmin's config.inc.php for available options.";
+      description = lib.mdDoc "Extra configuration for the postfixadmin instance, see postfixadmin's config.inc.php for available options.";
     };
   };
 
@@ -177,7 +177,7 @@ in
 
     services.phpfpm.pools.postfixadmin = {
       user = user;
-      phpPackage = pkgs.php74;
+      phpPackage = pkgs.php81;
       phpOptions = ''
         error_log = 'stderr'
         log_errors = on
diff --git a/nixos/modules/services/mail/postgrey.nix b/nixos/modules/services/mail/postgrey.nix
index 7c206e3725e6..fdfa08946ddf 100644
--- a/nixos/modules/services/mail/postgrey.nix
+++ b/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";
       };
     };
   };
@@ -59,7 +59,7 @@ in {
       enable = mkOption {
         type = bool;
         default = false;
-        description = "Whether to run the Postgrey daemon";
+        description = lib.mdDoc "Whether to run the Postgrey daemon";
       };
       socket = mkOption {
         type = socket;
@@ -71,73 +71,73 @@ in {
           addr = "127.0.0.1";
           port = 10030;
         };
-        description = "Socket to bind to";
+        description = lib.mdDoc "Socket to bind to";
       };
       greylistText = mkOption {
         type = str;
         default = "Greylisted for %%s seconds";
-        description = "Response status text for greylisted messages; use %%s for seconds left until greylisting is over and %%r for mail domain of recipient";
+        description = lib.mdDoc "Response status text for greylisted messages; use %%s for seconds left until greylisting is over and %%r for mail domain of recipient";
       };
       greylistAction = mkOption {
         type = str;
         default = "DEFER_IF_PERMIT";
-        description = "Response status for greylisted messages (see access(5))";
+        description = lib.mdDoc "Response status for greylisted messages (see access(5))";
       };
       greylistHeader = mkOption {
         type = str;
         default = "X-Greylist: delayed %%t seconds by postgrey-%%v at %%h; %%d";
-        description = "Prepend header to greylisted mails; use %%t for seconds delayed due to greylisting, %%v for the version of postgrey, %%d for the date, and %%h for the host";
+        description = lib.mdDoc "Prepend header to greylisted mails; use %%t for seconds delayed due to greylisting, %%v for the version of postgrey, %%d for the date, and %%h for the host";
       };
       delay = mkOption {
         type = natural;
         default = 300;
-        description = "Greylist for N seconds";
+        description = lib.mdDoc "Greylist for N seconds";
       };
       maxAge = mkOption {
         type = natural;
         default = 35;
-        description = "Delete entries from whitelist if they haven't been seen for N days";
+        description = lib.mdDoc "Delete entries from whitelist if they haven't been seen for N days";
       };
       retryWindow = mkOption {
         type = either str natural;
         default = 2;
         example = "12h";
-        description = "Allow N days for the first retry. Use string with appended 'h' to specify time in hours";
+        description = lib.mdDoc "Allow N days for the first retry. Use string with appended 'h' to specify time in hours";
       };
       lookupBySubnet = mkOption {
         type = bool;
         default = true;
-        description = "Strip the last N bits from IP addresses, determined by IPv4CIDR and IPv6CIDR";
+        description = lib.mdDoc "Strip the last N bits from IP addresses, determined by IPv4CIDR and IPv6CIDR";
       };
       IPv4CIDR = mkOption {
         type = natural;
         default = 24;
-        description = "Strip N bits from IPv4 addresses if lookupBySubnet is true";
+        description = lib.mdDoc "Strip N bits from IPv4 addresses if lookupBySubnet is true";
       };
       IPv6CIDR = mkOption {
         type = natural;
         default = 64;
-        description = "Strip N bits from IPv6 addresses if lookupBySubnet is true";
+        description = lib.mdDoc "Strip N bits from IPv6 addresses if lookupBySubnet is true";
       };
       privacy = mkOption {
         type = bool;
         default = true;
-        description = "Store data using one-way hash functions (SHA1)";
+        description = lib.mdDoc "Store data using one-way hash functions (SHA1)";
       };
       autoWhitelist = mkOption {
         type = nullOr natural';
         default = 5;
-        description = "Whitelist clients after successful delivery of N messages";
+        description = lib.mdDoc "Whitelist clients after successful delivery of N messages";
       };
       whitelistClients = mkOption {
         type = listOf path;
         default = [];
-        description = "Client address whitelist files (see postgrey(8))";
+        description = lib.mdDoc "Client address whitelist files (see postgrey(8))";
       };
       whitelistRecipients = mkOption {
         type = listOf path;
         default = [];
-        description = "Recipient address whitelist files (see postgrey(8))";
+        description = lib.mdDoc "Recipient address whitelist files (see postgrey(8))";
       };
     };
   };
diff --git a/nixos/modules/services/mail/postsrsd.nix b/nixos/modules/services/mail/postsrsd.nix
index 2ebc675ab10a..41301c8697d7 100644
--- a/nixos/modules/services/mail/postsrsd.nix
+++ b/nixos/modules/services/mail/postsrsd.nix
@@ -17,24 +17,24 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the postsrsd SRS server for Postfix.";
+        description = lib.mdDoc "Whether to enable the postsrsd SRS server for Postfix.";
       };
 
       secretsFile = mkOption {
         type = types.path;
         default = "/var/lib/postsrsd/postsrsd.secret";
-        description = "Secret keys used for signing and verification";
+        description = lib.mdDoc "Secret keys used for signing and verification";
       };
 
       domain = mkOption {
         type = types.str;
-        description = "Domain name for rewrite";
+        description = lib.mdDoc "Domain name for rewrite";
       };
 
       separator = mkOption {
         type = types.enum ["-" "=" "+"];
         default = "=";
-        description = "First separator character in generated addresses";
+        description = lib.mdDoc "First separator character in generated addresses";
       };
 
       # bindAddress = mkOption { # uncomment once 1.5 is released
@@ -46,37 +46,37 @@ in {
       forwardPort = mkOption {
         type = types.int;
         default = 10001;
-        description = "Port for the forward SRS lookup";
+        description = lib.mdDoc "Port for the forward SRS lookup";
       };
 
       reversePort = mkOption {
         type = types.int;
         default = 10002;
-        description = "Port for the reverse SRS lookup";
+        description = lib.mdDoc "Port for the reverse SRS lookup";
       };
 
       timeout = mkOption {
         type = types.int;
         default = 1800;
-        description = "Timeout for idle client connections in seconds";
+        description = lib.mdDoc "Timeout for idle client connections in seconds";
       };
 
       excludeDomains = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Origin domains to exclude from rewriting in addition to primary domain";
+        description = lib.mdDoc "Origin domains to exclude from rewriting in addition to primary domain";
       };
 
       user = mkOption {
         type = types.str;
         default = "postsrsd";
-        description = "User for the daemon";
+        description = lib.mdDoc "User for the daemon";
       };
 
       group = mkOption {
         type = types.str;
         default = "postsrsd";
-        description = "Group for the daemon";
+        description = lib.mdDoc "Group for the daemon";
       };
 
     };
diff --git a/nixos/modules/services/mail/public-inbox.nix b/nixos/modules/services/mail/public-inbox.nix
new file mode 100644
index 000000000000..ab7ff5f726a4
--- /dev/null
+++ b/nixos/modules/services/mail/public-inbox.nix
@@ -0,0 +1,577 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.public-inbox;
+  stateDir = "/var/lib/public-inbox";
+
+  gitIni = pkgs.formats.gitIni { listsAsDuplicateKeys = true; };
+  iniAtom = elemAt gitIni.type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped/*either*/.functor.wrapped 0;
+
+  useSpamAssassin = cfg.settings.publicinboxmda.spamcheck == "spamc" ||
+                    cfg.settings.publicinboxwatch.spamcheck == "spamc";
+
+  publicInboxDaemonOptions = proto: defaultPort: {
+    args = mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = lib.mdDoc "Command-line arguments to pass to {manpage}`public-inbox-${proto}d(1)`.";
+    };
+    port = mkOption {
+      type = with types; nullOr (either str port);
+      default = defaultPort;
+      description = lib.mdDoc ''
+        Listening port.
+        Beware that public-inbox uses well-known ports number to decide whether to enable TLS or not.
+        Set to null and use `systemd.sockets.public-inbox-${proto}d.listenStreams`
+        if you need a more advanced listening.
+      '';
+    };
+    cert = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "/path/to/fullchain.pem";
+      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 = lib.mdDoc "Path to TLS key to use for connections to {manpage}`public-inbox-${proto}d(1)`.";
+    };
+  };
+
+  serviceConfig = srv:
+    let proto = removeSuffix "d" srv;
+        needNetwork = builtins.hasAttr proto cfg && cfg.${proto}.port == null;
+    in {
+    serviceConfig = {
+      # Enable JIT-compiled C (via Inline::C)
+      Environment = [ "PERL_INLINE_DIRECTORY=/run/public-inbox-${srv}/perl-inline" ];
+      # NonBlocking is REQUIRED to avoid a race condition
+      # if running simultaneous services.
+      NonBlocking = true;
+      #LimitNOFILE = 30000;
+      User = config.users.users."public-inbox".name;
+      Group = config.users.groups."public-inbox".name;
+      RuntimeDirectory = [
+          "public-inbox-${srv}/perl-inline"
+        ];
+      RuntimeDirectoryMode = "700";
+      # This is for BindPaths= and BindReadOnlyPaths=
+      # to allow traversal of directories they create inside RootDirectory=
+      UMask = "0066";
+      StateDirectory = ["public-inbox"];
+      StateDirectoryMode = "0750";
+      WorkingDirectory = stateDir;
+      BindReadOnlyPaths = [
+          "/etc"
+          "/run/systemd"
+          "${config.i18n.glibcLocales}"
+        ] ++
+        mapAttrsToList (name: inbox: inbox.description) cfg.inboxes ++
+        # Without confinement the whole Nix store
+        # is made available to the service
+        optionals (!config.systemd.services."public-inbox-${srv}".confinement.enable) [
+          "${pkgs.dash}/bin/dash:/bin/sh"
+          builtins.storeDir
+        ];
+      # The following options are only for optimizing:
+      # systemd-analyze security public-inbox-'*'
+      AmbientCapabilities = "";
+      CapabilityBoundingSet = "";
+      # ProtectClock= adds DeviceAllow=char-rtc r
+      DeviceAllow = "";
+      LockPersonality = true;
+      MemoryDenyWriteExecute = true;
+      NoNewPrivileges = true;
+      PrivateNetwork = mkDefault (!needNetwork);
+      ProcSubset = "pid";
+      ProtectClock = true;
+      ProtectHome = mkDefault true;
+      ProtectHostname = true;
+      ProtectKernelLogs = true;
+      ProtectProc = "invisible";
+      #ProtectSystem = "strict";
+      RemoveIPC = true;
+      RestrictAddressFamilies = [ "AF_UNIX" ] ++
+        optionals needNetwork [ "AF_INET" "AF_INET6" ];
+      RestrictNamespaces = true;
+      RestrictRealtime = true;
+      RestrictSUIDSGID = true;
+      SystemCallFilter = [
+        "@system-service"
+        "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources"
+        # Not removing @setuid and @privileged because Inline::C needs them.
+        # Not removing @timer because git upload-pack needs it.
+      ];
+      SystemCallArchitectures = "native";
+
+      # The following options are redundant when confinement is enabled
+      RootDirectory = "/var/empty";
+      TemporaryFileSystem = "/";
+      PrivateMounts = true;
+      MountAPIVFS = true;
+      PrivateDevices = true;
+      PrivateTmp = true;
+      PrivateUsers = true;
+      ProtectControlGroups = true;
+      ProtectKernelModules = true;
+      ProtectKernelTunables = true;
+    };
+    confinement = {
+      # Until we agree upon doing it directly here in NixOS
+      # https://github.com/NixOS/nixpkgs/pull/104457#issuecomment-1115768447
+      # let the user choose to enable the confinement with:
+      # systemd.services.public-inbox-httpd.confinement.enable = true;
+      # systemd.services.public-inbox-imapd.confinement.enable = true;
+      # systemd.services.public-inbox-init.confinement.enable = true;
+      # systemd.services.public-inbox-nntpd.confinement.enable = true;
+      #enable = true;
+      mode = "full-apivfs";
+      # Inline::C needs a /bin/sh, and dash is enough
+      binSh = "${pkgs.dash}/bin/dash";
+      packages = [
+          pkgs.iana-etc
+          (getLib pkgs.nss)
+          pkgs.tzdata
+        ];
+    };
+  };
+in
+
+{
+  options.services.public-inbox = {
+    enable = mkEnableOption (lib.mdDoc "the public-inbox mail archiver");
+    package = mkOption {
+      type = types.package;
+      default = pkgs.public-inbox;
+      defaultText = literalExpression "pkgs.public-inbox";
+      description = lib.mdDoc "public-inbox package to use.";
+    };
+    path = mkOption {
+      type = with types; listOf package;
+      default = [];
+      example = literalExpression "with pkgs; [ spamassassin ]";
+      description = lib.mdDoc ''
+        Additional packages to place in the path of public-inbox-mda,
+        public-inbox-watch, etc.
+      '';
+    };
+    inboxes = mkOption {
+      description = lib.mdDoc ''
+        Inboxes to configure, where attribute names are inbox names.
+      '';
+      default = {};
+      type = types.attrsOf (types.submodule ({name, ...}: {
+        freeformType = types.attrsOf iniAtom;
+        options.inboxdir = mkOption {
+          type = types.str;
+          default = "${stateDir}/inboxes/${name}";
+          description = lib.mdDoc "The absolute path to the directory which hosts the public-inbox.";
+        };
+        options.address = mkOption {
+          type = with types; listOf str;
+          example = "example-discuss@example.org";
+          description = lib.mdDoc "The email addresses of the public-inbox.";
+        };
+        options.url = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          example = "https://example.org/lists/example-discuss";
+          description = lib.mdDoc "URL where this inbox can be accessed over HTTP.";
+        };
+        options.description = mkOption {
+          type = types.str;
+          example = "user/dev discussion of public-inbox itself";
+          description = lib.mdDoc "User-visible description for the repository.";
+          apply = pkgs.writeText "public-inbox-description-${name}";
+        };
+        options.newsgroup = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = lib.mdDoc "NNTP group name for the inbox.";
+        };
+        options.watch = mkOption {
+          type = with types; listOf str;
+          default = [];
+          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 = lib.mdDoc ''
+            If specified, {manpage}`public-inbox-watch(1)` will only process
+            mail containing a matching header.
+          '';
+        };
+        options.coderepo = mkOption {
+          type = (types.listOf (types.enum (attrNames cfg.settings.coderepo))) // {
+            description = "list of coderepo names";
+          };
+          default = [];
+          description = lib.mdDoc "Nicknames of a 'coderepo' section associated with the inbox.";
+        };
+      }));
+    };
+    imap = {
+      enable = mkEnableOption (lib.mdDoc "the public-inbox IMAP server");
+    } // publicInboxDaemonOptions "imap" 993;
+    http = {
+      enable = mkEnableOption (lib.mdDoc "the public-inbox HTTP server");
+      mounts = mkOption {
+        type = with types; listOf str;
+        default = [ "/" ];
+        example = [ "/lists/archives" ];
+        description = lib.mdDoc ''
+          Root paths or URLs that public-inbox will be served on.
+          If domain parts are present, only requests to those
+          domains will be accepted.
+        '';
+      };
+      args = (publicInboxDaemonOptions "http" 80).args;
+      port = mkOption {
+        type = with types; nullOr (either str port);
+        default = 80;
+        example = "/run/public-inbox-httpd.sock";
+        description = lib.mdDoc ''
+          Listening port or systemd's ListenStream= entry
+          to be used as a reverse proxy, eg. in nginx:
+          `locations."/inbox".proxyPass = "http://unix:''${config.services.public-inbox.http.port}:/inbox";`
+          Set to null and use `systemd.sockets.public-inbox-httpd.listenStreams`
+          if you need a more advanced listening.
+        '';
+      };
+    };
+    mda = {
+      enable = mkEnableOption (lib.mdDoc "the public-inbox Mail Delivery Agent");
+      args = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = lib.mdDoc "Command-line arguments to pass to {manpage}`public-inbox-mda(1)`.";
+      };
+    };
+    postfix.enable = mkEnableOption (lib.mdDoc "the integration into Postfix");
+    nntp = {
+      enable = mkEnableOption (lib.mdDoc "the public-inbox NNTP server");
+    } // publicInboxDaemonOptions "nntp" 563;
+    spamAssassinRules = mkOption {
+      type = with types; nullOr path;
+      default = "${cfg.package.sa_config}/user/.spamassassin/user_prefs";
+      defaultText = literalExpression "\${cfg.package.sa_config}/user/.spamassassin/user_prefs";
+      description = lib.mdDoc "SpamAssassin configuration specific to public-inbox.";
+    };
+    settings = mkOption {
+      description = lib.mdDoc ''
+        Settings for the [public-inbox config file](https://public-inbox.org/public-inbox-config.html).
+      '';
+      default = {};
+      type = types.submodule {
+        freeformType = gitIni.type;
+        options.publicinbox = mkOption {
+          default = {};
+          description = lib.mdDoc "public inboxes";
+          type = types.submodule {
+            freeformType = with types; /*inbox name*/attrsOf (/*inbox option name*/attrsOf /*inbox option value*/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.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.wwwlisting = mkOption {
+              type = with types; enum [ "all" "404" "match=domain" ];
+              default = "404";
+              description = lib.mdDoc ''
+                Controls which lists (if any) are listed for when the root
+                public-inbox URL is accessed over HTTP.
+              '';
+            };
+          };
+        };
+        options.publicinboxmda.spamcheck = mkOption {
+          type = with types; enum [ "spamc" "none" ];
+          default = "none";
+          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 = lib.mdDoc ''
+            If set to spamc, {manpage}`public-inbox-watch(1)` will filter spam
+            using SpamAssassin.
+          '';
+        };
+        options.publicinboxwatch.watchspam = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          example = "maildir:/path/to/spam";
+          description = lib.mdDoc ''
+            If set, mail in this maildir will be trained as spam and
+            deleted from all watched inboxes
+          '';
+        };
+        options.coderepo = mkOption {
+          default = {};
+          description = lib.mdDoc "code repositories";
+          type = types.attrsOf (types.submodule {
+            freeformType = types.attrsOf iniAtom;
+            options.cgitUrl = mkOption {
+              type = types.str;
+              description = lib.mdDoc "URL of a cgit instance";
+            };
+            options.dir = mkOption {
+              type = types.str;
+              description = lib.mdDoc "Path to a git repository";
+            };
+          });
+        };
+      };
+    };
+    openFirewall = mkEnableOption (lib.mdDoc "opening the firewall when using a port option");
+  };
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = config.services.spamassassin.enable || !useSpamAssassin;
+        message = ''
+          public-inbox is configured to use SpamAssassin, but
+          services.spamassassin.enable is false.  If you don't need
+          spam checking, set `services.public-inbox.settings.publicinboxmda.spamcheck' and
+          `services.public-inbox.settings.publicinboxwatch.spamcheck' to null.
+        '';
+      }
+      { assertion = cfg.path != [] || !useSpamAssassin;
+        message = ''
+          public-inbox is configured to use SpamAssassin, but there is
+          no spamc executable in services.public-inbox.path.  If you
+          don't need spam checking, set
+          `services.public-inbox.settings.publicinboxmda.spamcheck' and
+          `services.public-inbox.settings.publicinboxwatch.spamcheck' to null.
+        '';
+      }
+    ];
+    services.public-inbox.settings =
+      filterAttrsRecursive (n: v: v != null) {
+        publicinbox = mapAttrs (n: filterAttrs (n: v: n != "description")) cfg.inboxes;
+    };
+    users = {
+      users.public-inbox = {
+        home = stateDir;
+        group = "public-inbox";
+        isSystemUser = true;
+      };
+      groups.public-inbox = {};
+    };
+    networking.firewall = mkIf cfg.openFirewall
+      { allowedTCPPorts = mkMerge
+        (map (proto: (mkIf (cfg.${proto}.enable && types.port.check cfg.${proto}.port) [ cfg.${proto}.port ]))
+        ["imap" "http" "nntp"]);
+      };
+    services.postfix = mkIf (cfg.postfix.enable && cfg.mda.enable) {
+      # Not sure limiting to 1 is necessary, but better safe than sorry.
+      config.public-inbox_destination_recipient_limit = "1";
+
+      # Register the addresses as existing
+      virtual =
+        concatStringsSep "\n" (mapAttrsToList (_: inbox:
+          concatMapStringsSep "\n" (address:
+            "${address} ${address}"
+          ) inbox.address
+        ) cfg.inboxes);
+
+      # Deliver the addresses with the public-inbox transport
+      transport =
+        concatStringsSep "\n" (mapAttrsToList (_: inbox:
+          concatMapStringsSep "\n" (address:
+            "${address} public-inbox:${address}"
+          ) inbox.address
+        ) cfg.inboxes);
+
+      # The public-inbox transport
+      masterConfig.public-inbox = {
+        type = "unix";
+        privileged = true; # Required for user=
+        command = "pipe";
+        args = [
+          "flags=X" # Report as a final delivery
+          "user=${with config.users; users."public-inbox".name + ":" + groups."public-inbox".name}"
+          # Specifying a nexthop when using the transport
+          # (eg. test public-inbox:test) allows to
+          # receive mails with an extension (eg. test+foo).
+          "argv=${pkgs.writeShellScript "public-inbox-transport" ''
+            export HOME="${stateDir}"
+            export ORIGINAL_RECIPIENT="''${2:-1}"
+            export PATH="${makeBinPath cfg.path}:$PATH"
+            exec ${cfg.package}/bin/public-inbox-mda ${escapeShellArgs cfg.mda.args}
+          ''} \${original_recipient} \${nexthop}"
+        ];
+      };
+    };
+    systemd.sockets = mkMerge (map (proto:
+      mkIf (cfg.${proto}.enable && cfg.${proto}.port != null)
+        { "public-inbox-${proto}d" = {
+            listenStreams = [ (toString cfg.${proto}.port) ];
+            wantedBy = [ "sockets.target" ];
+          };
+        }
+      ) [ "imap" "http" "nntp" ]);
+    systemd.services = mkMerge [
+      (mkIf cfg.imap.enable
+        { public-inbox-imapd = mkMerge [(serviceConfig "imapd") {
+          after = [ "public-inbox-init.service" "public-inbox-watch.service" ];
+          requires = [ "public-inbox-init.service" ];
+          serviceConfig = {
+            ExecStart = escapeShellArgs (
+              [ "${cfg.package}/bin/public-inbox-imapd" ] ++
+              cfg.imap.args ++
+              optionals (cfg.imap.cert != null) [ "--cert" cfg.imap.cert ] ++
+              optionals (cfg.imap.key != null) [ "--key" cfg.imap.key ]
+            );
+          };
+        }];
+      })
+      (mkIf cfg.http.enable
+        { public-inbox-httpd = mkMerge [(serviceConfig "httpd") {
+          after = [ "public-inbox-init.service" "public-inbox-watch.service" ];
+          requires = [ "public-inbox-init.service" ];
+          serviceConfig = {
+            ExecStart = escapeShellArgs (
+              [ "${cfg.package}/bin/public-inbox-httpd" ] ++
+              cfg.http.args ++
+              # See https://public-inbox.org/public-inbox.git/tree/examples/public-inbox.psgi
+              # for upstream's example.
+              [ (pkgs.writeText "public-inbox.psgi" ''
+                #!${cfg.package.fullperl} -w
+                use strict;
+                use warnings;
+                use Plack::Builder;
+                use PublicInbox::WWW;
+
+                my $www = PublicInbox::WWW->new;
+                $www->preload;
+
+                builder {
+                  # If reached through a reverse proxy,
+                  # make it transparent by resetting some HTTP headers
+                  # used by public-inbox to generate URIs.
+                  enable 'ReverseProxy';
+
+                  # No need to send a response body if it's an HTTP HEAD requests.
+                  enable 'Head';
+
+                  # Route according to configured domains and root paths.
+                  ${concatMapStrings (path: ''
+                  mount q(${path}) => sub { $www->call(@_); };
+                  '') cfg.http.mounts}
+                }
+              '') ]
+            );
+          };
+        }];
+      })
+      (mkIf cfg.nntp.enable
+        { public-inbox-nntpd = mkMerge [(serviceConfig "nntpd") {
+          after = [ "public-inbox-init.service" "public-inbox-watch.service" ];
+          requires = [ "public-inbox-init.service" ];
+          serviceConfig = {
+            ExecStart = escapeShellArgs (
+              [ "${cfg.package}/bin/public-inbox-nntpd" ] ++
+              cfg.nntp.args ++
+              optionals (cfg.nntp.cert != null) [ "--cert" cfg.nntp.cert ] ++
+              optionals (cfg.nntp.key != null) [ "--key" cfg.nntp.key ]
+            );
+          };
+        }];
+      })
+      (mkIf (any (inbox: inbox.watch != []) (attrValues cfg.inboxes)
+        || cfg.settings.publicinboxwatch.watchspam != null)
+        { public-inbox-watch = mkMerge [(serviceConfig "watch") {
+          inherit (cfg) path;
+          wants = [ "public-inbox-init.service" ];
+          requires = [ "public-inbox-init.service" ] ++
+            optional (cfg.settings.publicinboxwatch.spamcheck == "spamc") "spamassassin.service";
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            ExecStart = "${cfg.package}/bin/public-inbox-watch";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          };
+        }];
+      })
+      ({ public-inbox-init = let
+          PI_CONFIG = gitIni.generate "public-inbox.ini"
+            (filterAttrsRecursive (n: v: v != null) cfg.settings);
+          in mkMerge [(serviceConfig "init") {
+          wantedBy = [ "multi-user.target" ];
+          restartIfChanged = true;
+          restartTriggers = [ PI_CONFIG ];
+          script = ''
+            set -ux
+            install -D -p ${PI_CONFIG} ${stateDir}/.public-inbox/config
+            '' + optionalString useSpamAssassin ''
+              install -m 0700 -o spamd -d ${stateDir}/.spamassassin
+              ${optionalString (cfg.spamAssassinRules != null) ''
+                ln -sf ${cfg.spamAssassinRules} ${stateDir}/.spamassassin/user_prefs
+              ''}
+            '' + concatStrings (mapAttrsToList (name: inbox: ''
+              if [ ! -e ${stateDir}/inboxes/${escapeShellArg name} ]; then
+                # public-inbox-init creates an inbox and adds it to a config file.
+                # It tries to atomically write the config file by creating
+                # another file in the same directory, and renaming it.
+                # This has the sad consequence that we can't use
+                # /dev/null, or it would try to create a file in /dev.
+                conf_dir="$(mktemp -d)"
+
+                PI_CONFIG=$conf_dir/conf \
+                ${cfg.package}/bin/public-inbox-init -V2 \
+                  ${escapeShellArgs ([ name "${stateDir}/inboxes/${name}" inbox.url ] ++ inbox.address)}
+
+                rm -rf $conf_dir
+              fi
+
+              ln -sf ${inbox.description} \
+                ${stateDir}/inboxes/${escapeShellArg name}/description
+
+              export GIT_DIR=${stateDir}/inboxes/${escapeShellArg name}/all.git
+              if test -d "$GIT_DIR"; then
+                # Config is inherited by each epoch repository,
+                # so just needs to be set for all.git.
+                ${pkgs.git}/bin/git config core.sharedRepository 0640
+              fi
+            '') cfg.inboxes
+            ) + ''
+            shopt -s nullglob
+            for inbox in ${stateDir}/inboxes/*/; do
+              # This should be idempotent, but only do it for new
+              # inboxes anyway because it's only needed once, and could
+              # be slow for large pre-existing inboxes.
+              ls -1 "$inbox" | grep -q '^xap' ||
+              ${cfg.package}/bin/public-inbox-index "$inbox"
+            done
+          '';
+          serviceConfig = {
+            Type = "oneshot";
+            RemainAfterExit = true;
+            StateDirectory = [
+              "public-inbox/.public-inbox"
+              "public-inbox/.public-inbox/emergency"
+              "public-inbox/inboxes"
+            ];
+          };
+        }];
+      })
+    ];
+    environment.systemPackages = with pkgs; [ cfg.package ];
+  };
+  meta.maintainers = with lib.maintainers; [ julm qyliss ];
+}
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 1dd393da8822..e05820fb87cf 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -14,19 +14,19 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable roundcube.
 
         Also enables nginx virtual host management.
-        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
-        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+        Further nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
+        See [](#opt-services.nginx.virtualHosts) for further information.
       '';
     };
 
     hostName = mkOption {
       type = types.str;
       example = "webmail.example.com";
-      description = "Hostname to use for the nginx vhost";
+      description = lib.mdDoc "Hostname to use for the nginx vhost";
     };
 
     package = mkOption {
@@ -38,8 +38,8 @@ in
         roundcube.withPlugins (plugins: [ plugins.persistent_login ])
       '';
 
-      description = ''
-        The package which contains roundcube's sources. Can be overriden to create
+      description = lib.mdDoc ''
+        The package which contains roundcube's sources. Can be overridden to create
         an environment which contains roundcube and third-party plugins.
       '';
     };
@@ -48,41 +48,41 @@ in
       username = mkOption {
         type = types.str;
         default = "roundcube";
-        description = ''
+        description = lib.mdDoc ''
           Username for the postgresql connection.
-          If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well.
+          If `database.host` is set to `localhost`, a unix user and group of the same name will be created as well.
         '';
       };
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           Host of the postgresql server. If this is not set to
-          <literal>localhost</literal>, you have to create the
+          `localhost`, you have to create the
           postgresql user and database yourself, with appropriate
           permissions.
         '';
       };
       password = mkOption {
         type = types.str;
-        description = "Password for the postgresql connection. Do not use: the password will be stored world readable in the store; use <literal>passwordFile</literal> instead.";
+        description = lib.mdDoc "Password for the postgresql connection. Do not use: the password will be stored world readable in the store; use `passwordFile` instead.";
         default = "";
       };
       passwordFile = mkOption {
         type = types.str;
-        description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>. Ignored if <literal>database.host</literal> is set to <literal>localhost</literal>, as peer authentication will be used.";
+        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.";
       };
       dbname = mkOption {
         type = types.str;
         default = "roundcube";
-        description = "Name of the postgresql database";
+        description = lib.mdDoc "Name of the postgresql database";
       };
     };
 
     plugins = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         List of roundcube plugins to enable. Currently, only those directly shipped with Roundcube are supported.
       '';
     };
@@ -91,19 +91,19 @@ in
       type = types.listOf types.package;
       default = [];
       example = literalExpression "with pkgs.aspellDicts; [ en fr de ]";
-      description = ''
-        List of aspell dictionnaries for spell checking. If empty, spell checking is disabled.
+      description = lib.mdDoc ''
+        List of aspell dictionaries for spell checking. If empty, spell checking is disabled.
       '';
     };
 
     maxAttachmentSize = mkOption {
       type = types.int;
       default = 18;
-      description = ''
+      description = lib.mdDoc ''
         The maximum attachment size in MB.
 
         Note: Since roundcube only uses 70% of max upload values configured in php
-        30% is added automatically to <xref linkend="opt-services.roundcube.maxAttachmentSize"/>.
+        30% is added automatically to [](#opt-services.roundcube.maxAttachmentSize).
       '';
       apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M";
     };
@@ -111,7 +111,7 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = "Extra configuration for roundcube webmail instance";
+      description = lib.mdDoc "Extra configuration for roundcube webmail instance";
     };
   };
 
diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix
index a570e137a55a..f9be9024dd4f 100644
--- a/nixos/modules/services/mail/rspamd.nix
+++ b/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;
@@ -52,21 +52,21 @@ let
       enable = mkOption {
         type = types.nullOr types.bool;
         default = null;
-        description = "Whether to run the rspamd worker.";
+        description = lib.mdDoc "Whether to run the rspamd worker.";
       };
       name = mkOption {
         type = types.nullOr types.str;
         default = name;
-        description = "Name of the worker";
+        description = lib.mdDoc "Name of the worker";
       };
       type = mkOption {
         type = types.nullOr (types.enum [
           "normal" "controller" "fuzzy" "rspamd_proxy" "lua" "proxy"
         ]);
-        description = ''
-          The type of this worker. The type <literal>proxy</literal> is
+        description = lib.mdDoc ''
+          The type of this worker. The type `proxy` is
           deprecated and only kept for backwards compatibility and should be
-          replaced with <literal>rspamd_proxy</literal>.
+          replaced with `rspamd_proxy`.
         '';
         apply = let
             from = "services.rspamd.workers.\"${name}\".type";
@@ -77,7 +77,7 @@ let
       bindSockets = mkOption {
         type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of sockets to listen, in format acceptable by rspamd
         '';
         example = [{
@@ -94,21 +94,21 @@ let
       count = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Number of worker instances to run
         '';
       };
       includes = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of files to include in configuration
         '';
       };
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional entries to put verbatim into worker section of rspamd config file.";
+        description = lib.mdDoc "Additional entries to put verbatim into worker section of rspamd config file.";
       };
     };
     config = mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy") {
@@ -186,7 +186,7 @@ let
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether this file ${prefix} should be generated.  This
           option allows specific ${prefix} files to be disabled.
         '';
@@ -195,12 +195,12 @@ let
       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.";
       };
     };
     config = {
@@ -227,19 +227,19 @@ 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;
         default = false;
-        description = "Whether to run the rspamd daemon in debug mode.";
+        description = lib.mdDoc "Whether to run the rspamd daemon in debug mode.";
       };
 
       locals = mkOption {
         type = with types; attrsOf (submodule (configFileModule "locals"));
         default = {};
-        description = ''
-          Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>.
+        description = lib.mdDoc ''
+          Local configuration files, written into {file}`/etc/rspamd/local.d/{name}`.
         '';
         example = literalExpression ''
           { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
@@ -251,8 +251,8 @@ in
       overrides = mkOption {
         type = with types; attrsOf (submodule (configFileModule "overrides"));
         default = {};
-        description = ''
-          Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>.
+        description = lib.mdDoc ''
+          Overridden configuration files, written into {file}`/etc/rspamd/override.d/{name}`.
         '';
         example = literalExpression ''
           { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
@@ -264,15 +264,15 @@ in
       localLuaRules = mkOption {
         default = null;
         type = types.nullOr types.path;
-        description = ''
-          Path of file to link to <filename>/etc/rspamd/rspamd.local.lua</filename> for local
+        description = lib.mdDoc ''
+          Path of file to link to {file}`/etc/rspamd/rspamd.local.lua` for local
           rules written in Lua
         '';
       };
 
       workers = mkOption {
         type = with types; attrsOf (submodule workerOpts);
-        description = ''
+        description = lib.mdDoc ''
           Attribute set of workers to start.
         '';
         default = {
@@ -301,7 +301,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add at the end of the rspamd configuration
           file.
         '';
@@ -310,7 +310,7 @@ in
       user = mkOption {
         type = types.str;
         default = "rspamd";
-        description = ''
+        description = lib.mdDoc ''
           User to use when no root privileges are required.
         '';
       };
@@ -318,7 +318,7 @@ in
       group = mkOption {
         type = types.str;
         default = "rspamd";
-        description = ''
+        description = lib.mdDoc ''
           Group to use when no root privileges are required.
         '';
       };
@@ -327,12 +327,12 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = "Add rspamd milter to postfix main.conf";
+          description = lib.mdDoc "Add rspamd milter to postfix main.conf";
         };
 
         config = mkOption {
           type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
-          description = ''
+          description = lib.mdDoc ''
             Addon to postfix configuration
           '';
           default = {
diff --git a/nixos/modules/services/mail/rss2email.nix b/nixos/modules/services/mail/rss2email.nix
index 7f8d2adac648..54404c5b5f4c 100644
--- a/nixos/modules/services/mail/rss2email.nix
+++ b/nixos/modules/services/mail/rss2email.nix
@@ -15,57 +15,57 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable rss2email.";
+        description = lib.mdDoc "Whether to enable rss2email.";
       };
 
       to = mkOption {
         type = types.str;
-        description = "Mail address to which to send emails";
+        description = lib.mdDoc "Mail address to which to send emails";
       };
 
       interval = mkOption {
         type = types.str;
         default = "12h";
-        description = "How often to check the feeds, in systemd interval format";
+        description = lib.mdDoc "How often to check the feeds, in systemd interval format";
       };
 
       config = mkOption {
         type = with types; attrsOf (oneOf [ str int bool ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           The configuration to give rss2email.
 
-          Default will use system-wide <literal>sendmail</literal> to send the
+          Default will use system-wide `sendmail` to send the
           email. This is rss2email's default when running
-          <literal>r2e new</literal>.
+          `r2e new`.
 
           This set contains key-value associations that will be set in the
-          <literal>[DEFAULT]</literal> block along with the
-          <literal>to</literal> parameter.
+          `[DEFAULT]` block along with the
+          `to` parameter.
 
-          See <literal>man r2e</literal> for more information on which
+          See `man r2e` for more information on which
           parameters are accepted.
         '';
       };
 
       feeds = mkOption {
-        description = "The feeds to watch.";
+        description = lib.mdDoc "The feeds to watch.";
         type = types.attrsOf (types.submodule {
           options = {
             url = mkOption {
               type = types.str;
-              description = "The URL at which to fetch the feed.";
+              description = lib.mdDoc "The URL at which to fetch the feed.";
             };
 
             to = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Email address to which to send feed items.
 
-                If <literal>null</literal>, this will not be set in the
+                If `null`, this will not be set in the
                 configuration file, and rss2email will make it default to
-                <literal>rss2email.to</literal>.
+                `rss2email.to`.
               '';
             };
           };
@@ -110,7 +110,6 @@ in {
     in
     {
       preStart = ''
-        cp ${conf} /var/rss2email/conf.cfg
         if [ ! -f /var/rss2email/db.json ]; then
           echo '{"version":2,"feeds":[]}' > /var/rss2email/db.json
         fi
@@ -118,7 +117,7 @@ in {
       path = [ pkgs.system-sendmail ];
       serviceConfig = {
         ExecStart =
-          "${pkgs.rss2email}/bin/r2e -c /var/rss2email/conf.cfg -d /var/rss2email/db.json run";
+          "${pkgs.rss2email}/bin/r2e -c ${conf} -d /var/rss2email/db.json run";
         User = "rss2email";
       };
     };
diff --git a/nixos/modules/services/mail/schleuder.nix b/nixos/modules/services/mail/schleuder.nix
new file mode 100644
index 000000000000..2991418dd804
--- /dev/null
+++ b/nixos/modules/services/mail/schleuder.nix
@@ -0,0 +1,162 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.schleuder;
+  settingsFormat = pkgs.formats.yaml { };
+  postfixMap = entries: lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${value}") entries);
+  writePostfixMap = name: entries: pkgs.writeText name (postfixMap entries);
+  configScript = pkgs.writeScript "schleuder-cfg" ''
+    #!${pkgs.runtimeShell}
+    set -exuo pipefail
+    umask 0077
+    ${pkgs.yq}/bin/yq \
+      --slurpfile overrides <(${pkgs.yq}/bin/yq . <${lib.escapeShellArg cfg.extraSettingsFile}) \
+      < ${settingsFormat.generate "schleuder.yml" cfg.settings} \
+      '. * $overrides[0]' \
+      > /etc/schleuder/schleuder.yml
+    chown schleuder: /etc/schleuder/schleuder.yml
+  '';
+in
+{
+  options.services.schleuder = {
+    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.
+
+        Note that this is only handled by the postfix integration, and
+        the setup of the lists, their members and their keys has to be
+        performed separately via schleuder's API, using a tool such as
+        schleuder-cli.
+      '';
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [ "widget-team@example.com" "security@example.com" ];
+    };
+    /* maybe one day....
+      domains = lib.mkOption {
+      description = "Domains for which all mail should be handled by Schleuder.";
+      type = lib.types.listOf lib.types.str;
+      default = [];
+      example = ["securelists.example.com"];
+      };
+    */
+    settings = lib.mkOption {
+      description = lib.mdDoc ''
+        Settings for schleuder.yml.
+
+        Check the [example configuration](https://0xacab.org/schleuder/schleuder/blob/master/etc/schleuder.yml) for possible values.
+      '';
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+        options.keyserver = lib.mkOption {
+          type = lib.types.str;
+          description = lib.mdDoc ''
+            Key server from which to fetch and update keys.
+
+            Note that NixOS uses a different default from upstream, since the upstream default sks-keyservers.net is deprecated.
+          '';
+          default = "keys.openpgp.org";
+        };
+      };
+      default = { };
+    };
+    extraSettingsFile = lib.mkOption {
+      description = lib.mdDoc "YAML file to merge into the schleuder config at runtime. This can be used for secrets such as API keys.";
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+    };
+    listDefaults = lib.mkOption {
+      description = lib.mdDoc ''
+        Default settings for lists (list-defaults.yml).
+
+        Check the [example configuration](https://0xacab.org/schleuder/schleuder/-/blob/master/etc/list-defaults.yml) for possible values.
+      '';
+      type = settingsFormat.type;
+      default = { };
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !(cfg.settings.api ? valid_api_keys);
+        message = ''
+          services.schleuder.settings.api.valid_api_keys is set. Defining API keys via NixOS config results in them being copied to the world-readable Nix store. Please use the extraSettingsFile option to store API keys in a non-public location.
+        '';
+      }
+      {
+        assertion = !(lib.any (db: db ? password) (lib.attrValues cfg.settings.database or {}));
+        message = ''
+          A password is defined for at least one database in services.schleuder.settings.database. Defining passwords via NixOS config results in them being copied to the world-readable Nix store. Please use the extraSettingsFile option to store database passwords in a non-public location.
+        '';
+      }
+    ];
+    users.users.schleuder.isSystemUser = true;
+    users.users.schleuder.group = "schleuder";
+    users.groups.schleuder = {};
+    environment.systemPackages = [
+      pkgs.schleuder-cli
+    ];
+    services.postfix = lib.mkIf cfg.enablePostfix {
+      extraMasterConf = ''
+        schleuder  unix  -       n       n       -       -       pipe
+          flags=DRhu user=schleuder argv=/${pkgs.schleuder}/bin/schleuder work ''${recipient}
+      '';
+      transport = lib.mkIf (cfg.lists != [ ]) (postfixMap (lib.genAttrs cfg.lists (_: "schleuder:")));
+      extraConfig = ''
+        schleuder_destination_recipient_limit = 1
+      '';
+      # review: does this make sense?
+      localRecipients = lib.mkIf (cfg.lists != [ ]) cfg.lists;
+    };
+    systemd.services = let commonServiceConfig = {
+      # We would have liked to use DynamicUser, but since the default
+      # database is SQLite and lives in StateDirectory, and that same
+      # database needs to be readable from the postfix service, this
+      # isn't trivial to do.
+      User = "schleuder";
+      StateDirectory = "schleuder";
+      StateDirectoryMode = "0700";
+    }; in
+      {
+        schleuder-init = {
+          serviceConfig = commonServiceConfig // {
+            ExecStartPre = lib.mkIf (cfg.extraSettingsFile != null) [
+              "+${configScript}"
+            ];
+            ExecStart = [ "${pkgs.schleuder}/bin/schleuder install" ];
+            Type = "oneshot";
+          };
+        };
+        schleuder-api-daemon = {
+          after = [ "local-fs.target" "network.target" "schleuder-init.service" ];
+          wantedBy = [ "multi-user.target" ];
+          requires = [ "schleuder-init.service" ];
+          serviceConfig = commonServiceConfig // {
+            ExecStart = [ "${pkgs.schleuder}/bin/schleuder-api-daemon" ];
+          };
+        };
+        schleuder-weekly-key-maintenance = {
+          after = [ "local-fs.target" "network.target" ];
+          startAt = "weekly";
+          serviceConfig = commonServiceConfig // {
+            ExecStart = [
+              "${pkgs.schleuder}/bin/schleuder refresh_keys"
+              "${pkgs.schleuder}/bin/schleuder check_keys"
+            ];
+          };
+        };
+      };
+
+    environment.etc."schleuder/schleuder.yml" = lib.mkIf (cfg.extraSettingsFile == null) {
+      source = settingsFormat.generate "schleuder.yml" cfg.settings;
+    };
+    environment.etc."schleuder/list-defaults.yml".source = settingsFormat.generate "list-defaults.yml" cfg.listDefaults;
+
+    services.schleuder = {
+      #lists_dir = "/var/lib/schleuder.lists";
+      settings.filters_dir = lib.mkDefault "/var/lib/schleuder/filters";
+      settings.keyword_handlers_dir = lib.mkDefault "/var/lib/schleuder/keyword_handlers";
+    };
+  };
+}
diff --git a/nixos/modules/services/mail/spamassassin.nix b/nixos/modules/services/mail/spamassassin.nix
index ac878222b26a..49d1d9315985 100644
--- a/nixos/modules/services/mail/spamassassin.nix
+++ b/nixos/modules/services/mail/spamassassin.nix
@@ -12,33 +12,36 @@ in
   options = {
 
     services.spamassassin = {
-      enable = mkEnableOption "the SpamAssassin daemon";
+      enable = mkEnableOption (lib.mdDoc "the SpamAssassin daemon");
 
       debug = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to run the SpamAssassin daemon in debug mode";
+        description = lib.mdDoc "Whether to run the SpamAssassin daemon in debug mode";
       };
 
       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.
         '';
@@ -54,7 +57,7 @@ in
 
       initPreConf = mkOption {
         type = with types; either str path;
-        description = "The SpamAssassin init.pre config.";
+        description = lib.mdDoc "The SpamAssassin init.pre config.";
         apply = val: if builtins.isPath val then val else pkgs.writeText "init.pre" val;
         default =
         ''
@@ -135,7 +138,7 @@ in
         User = "spamd";
         Group = "spamd";
         StateDirectory = "spamassassin";
-        ExecStartPost = "+${pkgs.systemd}/bin/systemctl -q --no-block try-reload-or-restart spamd.service";
+        ExecStartPost = "+${config.systemd.package}/bin/systemctl -q --no-block try-reload-or-restart spamd.service";
       };
 
       script = ''
diff --git a/nixos/modules/services/mail/sympa.nix b/nixos/modules/services/mail/sympa.nix
index f3578bef96ea..7a5047b2bea5 100644
--- a/nixos/modules/services/mail/sympa.nix
+++ b/nixos/modules/services/mail/sympa.nix
@@ -80,15 +80,15 @@ 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;
       default = "en_US";
       example = "cs";
-      description = ''
+      description = lib.mdDoc ''
         Default Sympa language.
-        See <link xlink:href='https://github.com/sympa-community/sympa/tree/sympa-6.2/po/sympa' />
+        See <https://github.com/sympa-community/sympa/tree/sympa-6.2/po/sympa>
         for available options.
       '';
     };
@@ -96,7 +96,7 @@ in
     listMasters = mkOption {
       type = listOf str;
       example = [ "postmaster@sympa.example.org" ];
-      description = ''
+      description = lib.mdDoc ''
         The list of the email addresses of the listmasters
         (users authorized to perform global server commands).
       '';
@@ -106,9 +106,9 @@ in
       type = nullOr str;
       default = null;
       example = "lists.example.org";
-      description = ''
-        Main domain to be used in <filename>sympa.conf</filename>.
-        If <literal>null</literal>, one of the <option>services.sympa.domains</option> is chosen for you.
+      description = lib.mdDoc ''
+        Main domain to be used in {file}`sympa.conf`.
+        If `null`, one of the {option}`services.sympa.domains` is chosen for you.
       '';
     };
 
@@ -119,8 +119,8 @@ in
             type = nullOr str;
             default = null;
             example = "archive.example.org";
-            description = ''
-              Domain part of the web interface URL (no web interface for this domain if <literal>null</literal>).
+            description = lib.mdDoc ''
+              Domain part of the web interface URL (no web interface for this domain if `null`).
               DNS record of type A (or AAAA or CNAME) has to exist with this value.
             '';
           };
@@ -128,7 +128,7 @@ in
             type = str;
             default = "/";
             example = "/sympa";
-            description = "URL path part of the web interface.";
+            description = lib.mdDoc "URL path part of the web interface.";
           };
           settings = mkOption {
             type = attrsOf (oneOf [ str int bool ]);
@@ -136,9 +136,9 @@ in
             example = {
               default_max_list_members = 3;
             };
-            description = ''
-              The <filename>robot.conf</filename> configuration file as key value set.
-              See <link xlink:href='https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html' />
+            description = lib.mdDoc ''
+              The {file}`robot.conf` configuration file as key value set.
+              See <https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html>
               for list of configuration parameters.
             '';
           };
@@ -149,7 +149,7 @@ in
         };
       }));
 
-      description = ''
+      description = lib.mdDoc ''
         Email domains handled by this instance. There have
         to be MX records for keys of this attribute set.
       '';
@@ -172,36 +172,36 @@ in
         type = enum [ "SQLite" "PostgreSQL" "MySQL" ];
         default = "SQLite";
         example = "MySQL";
-        description = "Database engine to use.";
+        description = lib.mdDoc "Database engine to use.";
       };
 
       host = mkOption {
         type = nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Database host address.
 
-          For MySQL, use <literal>localhost</literal> to connect using Unix domain socket.
+          For MySQL, use `localhost` to connect using Unix domain socket.
 
-          For PostgreSQL, use path to directory (e.g. <filename>/run/postgresql</filename>)
+          For PostgreSQL, use path to directory (e.g. {file}`/run/postgresql`)
           to connect using Unix domain socket located in this directory.
 
-          Use <literal>null</literal> to fall back on Sympa default, or when using
-          <option>services.sympa.database.createLocally</option>.
+          Use `null` to fall back on Sympa default, or when using
+          {option}`services.sympa.database.createLocally`.
         '';
       };
 
       port = mkOption {
         type = nullOr port;
         default = null;
-        description = "Database port. Use <literal>null</literal> for default port.";
+        description = lib.mdDoc "Database port. Use `null` for default port.";
       };
 
       name = mkOption {
         type = str;
         default = if cfg.database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa";
         defaultText = literalExpression ''if database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa"'';
-        description = ''
+        description = lib.mdDoc ''
           Database name. When using SQLite this must be an absolute
           path to the database file.
         '';
@@ -210,22 +210,22 @@ in
       user = mkOption {
         type = nullOr str;
         default = user;
-        description = "Database user. The system user name is used as a default.";
+        description = lib.mdDoc "Database user. The system user name is used as a default.";
       };
 
       passwordFile = mkOption {
         type = nullOr path;
         default = null;
         example = "/run/keys/sympa-dbpassword";
-        description = ''
-          A file containing the password for <option>services.sympa.database.user</option>.
+        description = lib.mdDoc ''
+          A file containing the password for {option}`services.sympa.database.user`.
         '';
       };
 
       createLocally = mkOption {
         type = bool;
         default = true;
-        description = "Whether to create a local database automatically.";
+        description = lib.mdDoc "Whether to create a local database automatically.";
       };
     };
 
@@ -233,23 +233,23 @@ in
       enable = mkOption {
         type = bool;
         default = true;
-        description = "Whether to enable Sympa web interface.";
+        description = lib.mdDoc "Whether to enable Sympa web interface.";
       };
 
       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.<replaceable>name</replaceable></option>.
+          {option}`services.nginx.virtualHosts.«name»`.
         '';
       };
 
       https = mkOption {
         type = bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use HTTPS. When nginx integration is enabled, this option forces SSL and enables ACME.
           Please note that Sympa web interface always uses https links even when this option is disabled.
         '';
@@ -258,7 +258,7 @@ in
       fcgiProcs = mkOption {
         type = ints.positive;
         default = 2;
-        description = "Number of FastCGI processes to fork.";
+        description = lib.mdDoc "Number of FastCGI processes to fork.";
       };
     };
 
@@ -266,12 +266,12 @@ in
       type = mkOption {
         type = enum [ "postfix" "none" ];
         default = "postfix";
-        description = ''
-          Mail transfer agent (MTA) integration. Use <literal>none</literal> if you want to configure it yourself.
+        description = lib.mdDoc ''
+          Mail transfer agent (MTA) integration. Use `none` if you want to configure it yourself.
 
-          The <literal>postfix</literal> integration sets up local Postfix instance that will pass incoming
+          The `postfix` integration sets up local Postfix instance that will pass incoming
           messages from configured domains to Sympa. You still need to configure at least outgoing message
-          handling using e.g. <option>services.postfix.relayHost</option>.
+          handling using e.g. {option}`services.postfix.relayHost`.
         '';
       };
     };
@@ -285,9 +285,9 @@ in
           viewlogs_page_size = 50;
         }
       '';
-      description = ''
-        The <filename>sympa.conf</filename> configuration file as key value set.
-        See <link xlink:href='https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html' />
+      description = lib.mdDoc ''
+        The {file}`sympa.conf` configuration file as key value set.
+        See <https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html>
         for list of configuration parameters.
       '';
     };
@@ -298,16 +298,16 @@ in
           enable = mkOption {
             type = bool;
             default = true;
-            description = "Whether this file should be generated. This option allows specific files to be disabled.";
+            description = lib.mdDoc "Whether this file should be generated. This option allows specific files to be disabled.";
           };
           text = mkOption {
             default = null;
             type = nullOr lines;
-            description = "Text of the file.";
+            description = lib.mdDoc "Text of the file.";
           };
           source = mkOption {
             type = path;
-            description = "Path of the source file.";
+            description = lib.mdDoc "Path of the source file.";
           };
         };
 
@@ -321,7 +321,7 @@ in
           };
         }
       '';
-      description = "Set of files to be linked in <filename>${dataDir}</filename>.";
+      description = lib.mdDoc "Set of files to be linked in {file}`${dataDir}`.";
     };
   };
 
diff --git a/nixos/modules/services/misc/matrix-appservice-discord.nix b/nixos/modules/services/matrix/appservice-discord.nix
index 8a8c7f41e3cb..15f0f0cc0cdb 100644
--- a/nixos/modules/services/misc/matrix-appservice-discord.nix
+++ b/nixos/modules/services/matrix/appservice-discord.nix
@@ -14,7 +14,7 @@ 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");
 
       settings = mkOption rec {
         # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
@@ -40,23 +40,16 @@ in {
             };
           }
         '';
-        description = ''
-          <filename>config.yaml</filename> configuration as a Nix attribute set.
-          </para>
+        description = lib.mdDoc ''
+          {file}`config.yaml` configuration as a Nix attribute set.
 
-          <para>
           Configuration options should match those described in
-          <link xlink:href="https://github.com/Half-Shot/matrix-appservice-discord/blob/master/config/config.sample.yaml">
-          config.sample.yaml</link>.
-          </para>
+          [config.sample.yaml](https://github.com/Half-Shot/matrix-appservice-discord/blob/master/config/config.sample.yaml).
 
-          <para>
-          <option>config.bridge.domain</option> and <option>config.bridge.homeserverUrl</option>
+          {option}`config.bridge.domain` and {option}`config.bridge.homeserverUrl`
           should be set to match the public host name of the Matrix homeserver for webhooks and avatars to work.
-          </para>
 
-          <para>
-          Secret tokens should be specified using <option>environmentFile</option>
+          Secret tokens should be specified using {option}`environmentFile`
           instead of this world-readable attribute set.
         '';
       };
@@ -64,11 +57,11 @@ in {
       environmentFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           File containing environment variables to be passed to the matrix-appservice-discord service,
           in which secret tokens can be specified securely by defining values for
-          <literal>APPSERVICE_DISCORD_AUTH_CLIENT_I_D</literal> and
-          <literal>APPSERVICE_DISCORD_AUTH_BOT_TOKEN</literal>.
+          `APPSERVICE_DISCORD_AUTH_CLIENT_I_D` and
+          `APPSERVICE_DISCORD_AUTH_BOT_TOKEN`.
         '';
       };
 
@@ -76,7 +69,7 @@ in {
         type = types.str;
         default = "http://localhost:${toString cfg.port}";
         defaultText = literalExpression ''"http://localhost:''${toString config.${opt.port}}"'';
-        description = ''
+        description = lib.mdDoc ''
           The URL where the application service is listening for HS requests.
         '';
       };
@@ -84,7 +77,7 @@ in {
       port = mkOption {
         type = types.port;
         default = 9005; # from https://github.com/Half-Shot/matrix-appservice-discord/blob/master/package.json#L11
-        description = ''
+        description = lib.mdDoc ''
           Port number on which the bridge should listen for internal communication with the Matrix homeserver.
         '';
       };
@@ -92,7 +85,7 @@ in {
       localpart = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The user_id localpart to assign to the AS.
         '';
       };
@@ -103,7 +96,7 @@ in {
         defaultText = literalExpression ''
           optional config.services.matrix-synapse.enable "matrix-synapse.service"
         '';
-        description = ''
+        description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service,
           such as the Matrix homeserver if it's running on the same host.
         '';
@@ -144,7 +137,7 @@ in {
         PrivateTmp = true;
         WorkingDirectory = appDir;
         StateDirectory = baseNameOf dataDir;
-        UMask = 0027;
+        UMask = "0027";
         EnvironmentFile = cfg.environmentFile;
 
         ExecStart = ''
diff --git a/nixos/modules/services/misc/matrix-appservice-irc.nix b/nixos/modules/services/matrix/appservice-irc.nix
index b041c9c82c56..388553d4182e 100644
--- a/nixos/modules/services/misc/matrix-appservice-irc.nix
+++ b/nixos/modules/services/matrix/appservice-irc.nix
@@ -28,30 +28,30 @@ 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;
-      description = "The port to listen on";
+      description = lib.mdDoc "The port to listen on";
       default = 8009;
     };
 
     needBindingCap = mkOption {
       type = bool;
-      description = "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)";
+      description = lib.mdDoc "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)";
       default = false;
     };
 
     passwordEncryptionKeyLength = mkOption {
       type = ints.unsigned;
-      description = "Length of the key to encrypt IRC passwords with";
+      description = lib.mdDoc "Length of the key to encrypt IRC passwords with";
       default = 4096;
       example = 8192;
     };
 
     registrationUrl = mkOption {
       type = str;
-      description = ''
+      description = lib.mdDoc ''
         The URL where the application service is listening for homeserver requests,
         from the Matrix homeserver perspective.
       '';
@@ -60,14 +60,14 @@ in {
 
     localpart = mkOption {
       type = str;
-      description = "The user_id localpart to assign to the appservice";
+      description = lib.mdDoc "The user_id localpart to assign to the appservice";
       default = "appservice-irc";
     };
 
     settings = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Configuration for the appservice, see
-        <link xlink:href="https://github.com/matrix-org/matrix-appservice-irc/blob/${pkgs.matrix-appservice-irc.version}/config.sample.yaml"/>
+        <https://github.com/matrix-org/matrix-appservice-irc/blob/${pkgs.matrix-appservice-irc.version}/config.sample.yaml>
         for supported values
       '';
       default = {};
@@ -76,7 +76,7 @@ in {
 
         options = {
           homeserver = mkOption {
-            description = "Homeserver configuration";
+            description = lib.mdDoc "Homeserver configuration";
             default = {};
             type = submodule {
               freeformType = jsonType;
@@ -84,12 +84,12 @@ in {
               options = {
                 url = mkOption {
                   type = str;
-                  description = "The URL to the home server for client-server API calls";
+                  description = lib.mdDoc "The URL to the home server for client-server API calls";
                 };
 
                 domain = mkOption {
                   type = str;
-                  description = ''
+                  description = lib.mdDoc ''
                     The 'domain' part for user IDs on this home server. Usually
                     (but not always) is the "domain name" part of the homeserver URL.
                   '';
@@ -100,21 +100,21 @@ in {
 
           database = mkOption {
             default = {};
-            description = "Configuration for the database";
+            description = lib.mdDoc "Configuration for the database";
             type = submodule {
               freeformType = jsonType;
 
               options = {
                 engine = mkOption {
                   type = str;
-                  description = "Which database engine to use";
+                  description = lib.mdDoc "Which database engine to use";
                   default = "nedb";
                   example = "postgres";
                 };
 
                 connectionString = mkOption {
                   type = str;
-                  description = "The database connection string";
+                  description = lib.mdDoc "The database connection string";
                   default = "nedb://var/lib/matrix-appservice-irc/data";
                   example = "postgres://username:password@host:port/databasename";
                 };
@@ -124,14 +124,14 @@ in {
 
           ircService = mkOption {
             default = {};
-            description = "IRC bridge configuration";
+            description = lib.mdDoc "IRC bridge configuration";
             type = submodule {
               freeformType = jsonType;
 
               options = {
                 passwordEncryptionKeyPath = mkOption {
                   type = str;
-                  description = ''
+                  description = lib.mdDoc ''
                     Location of the key with which IRC passwords are encrypted
                     for storage. Will be generated on first run if not present.
                   '';
@@ -140,7 +140,7 @@ in {
 
                 servers = mkOption {
                   type = submodule { freeformType = jsonType; };
-                  description = "IRC servers to connect to";
+                  description = lib.mdDoc "IRC servers to connect to";
                 };
               };
             };
@@ -153,6 +153,9 @@ in {
     systemd.services.matrix-appservice-irc = {
       description = "Matrix-IRC bridge";
       before = [ "matrix-synapse.service" ]; # So the registration can be used by Synapse
+      after = lib.optionals (cfg.settings.database.engine == "postgres") [
+        "postgresql.service"
+      ];
       wantedBy = [ "multi-user.target" ];
 
       preStart = ''
diff --git a/nixos/modules/services/misc/matrix-conduit.nix b/nixos/modules/services/matrix/conduit.nix
index 108f64de7aa9..c8d89ed33f51 100644
--- a/nixos/modules/services/misc/matrix-conduit.nix
+++ b/nixos/modules/services/matrix/conduit.nix
@@ -11,11 +11,11 @@ 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;
-        description = "Extra Environment variables to pass to the conduit server.";
+        description = lib.mdDoc "Extra Environment variables to pass to the conduit server.";
         default = {};
         example = { RUST_BACKTRACE="yes"; };
       };
@@ -23,9 +23,8 @@ in
       package = mkOption {
         type = types.package;
         default = pkgs.matrix-conduit;
-        defaultText = "pkgs.matrix-conduit";
-        example = "pkgs.matrix-conduit";
-        description = ''
+        defaultText = lib.literalExpression "pkgs.matrix-conduit";
+        description = lib.mdDoc ''
           Package of the conduit matrix server to use.
         '';
       };
@@ -37,50 +36,50 @@ in
             global.server_name = mkOption {
               type = types.str;
               example = "example.com";
-              description = "The server_name is the name of this server. It is used as a suffix for user # and room ids.";
+              description = lib.mdDoc "The server_name is the name of this server. It is used as a suffix for user # and room ids.";
             };
             global.port = mkOption {
               type = types.port;
               default = 6167;
-              description = "The port Conduit will be running on. You need to set up a reverse proxy in your web server (e.g. apache or nginx), so all requests to /_matrix on port 443 and 8448 will be forwarded to the Conduit instance running on this port";
+              description = lib.mdDoc "The port Conduit will be running on. You need to set up a reverse proxy in your web server (e.g. apache or nginx), so all requests to /_matrix on port 443 and 8448 will be forwarded to the Conduit instance running on this port";
             };
             global.max_request_size = mkOption {
               type = types.ints.positive;
               default = 20000000;
-              description = "Max request size in bytes. Don't forget to also change it in the proxy.";
+              description = lib.mdDoc "Max request size in bytes. Don't forget to also change it in the proxy.";
             };
             global.allow_registration = mkOption {
               type = types.bool;
               default = false;
-              description = "Whether new users can register on this server.";
+              description = lib.mdDoc "Whether new users can register on this server.";
             };
             global.allow_encryption = mkOption {
               type = types.bool;
               default = true;
-              description = "Whether new encrypted rooms can be created. Note: existing rooms will continue to work.";
+              description = lib.mdDoc "Whether new encrypted rooms can be created. Note: existing rooms will continue to work.";
             };
             global.allow_federation = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether this server federates with other servers.
               '';
             };
             global.trusted_servers = mkOption {
               type = types.listOf types.str;
               default = [ "matrix.org" ];
-              description = "Servers trusted with signing server keys.";
+              description = lib.mdDoc "Servers trusted with signing server keys.";
             };
             global.address = mkOption {
               type = types.str;
               default = "::1";
-              description = "Address to listen on for connections by the reverse proxy/tls terminator.";
+              description = lib.mdDoc "Address to listen on for connections by the reverse proxy/tls terminator.";
             };
             global.database_path = mkOption {
               type = types.str;
               default = "/var/lib/matrix-conduit/";
               readOnly = true;
-              description = ''
+              description = lib.mdDoc ''
                 Path to the conduit database, the directory where conduit will save its data.
                 Note that due to using the DynamicUser feature of systemd, this value should not be changed
                 and is set to be read only.
@@ -90,7 +89,7 @@ in
               type = types.enum [ "sqlite" "rocksdb" ];
               default = "sqlite";
               example = "rocksdb";
-              description = ''
+              description = lib.mdDoc ''
                 The database backend for the service. Switching it on an existing
                 instance will require manual migration of data.
               '';
@@ -98,9 +97,9 @@ in
           };
         };
         default = {};
-        description = ''
+        description = lib.mdDoc ''
             Generates the conduit.toml configuration file. Refer to
-            <link xlink:href="https://gitlab.com/famedly/conduit/-/blob/master/conduit-example.toml"/>
+            <https://gitlab.com/famedly/conduit/-/blob/master/conduit-example.toml>
             for details on supported values.
             Note that database_path can not be edited because the service's reliance on systemd StateDir.
         '';
diff --git a/nixos/modules/services/misc/dendrite.nix b/nixos/modules/services/matrix/dendrite.nix
index 35bec40926ec..a5fea3da4844 100644
--- a/nixos/modules/services/misc/dendrite.nix
+++ b/nixos/modules/services/matrix/dendrite.nix
@@ -7,18 +7,18 @@ 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;
-      description = ''
+      description = lib.mdDoc ''
         The port to listen for HTTP requests on.
       '';
     };
     httpsPort = lib.mkOption {
       type = lib.types.nullOr lib.types.port;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The port to listen for HTTPS requests on.
       '';
     };
@@ -26,52 +26,62 @@ 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 {
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [ "private_key:/path/to/my_private_key" ];
+      description = lib.mdDoc ''
+        This can be used to pass secrets to the systemd service without adding them to
+        the nix store.
+        To use the example setting, see the example of
+        {option}`services.dendrite.settings.global.private_key`.
+        See the LoadCredential section of systemd.exec manual for more information.
       '';
     };
     settings = lib.mkOption {
@@ -81,29 +91,31 @@ in
           server_name = lib.mkOption {
             type = lib.types.str;
             example = "example.com";
-            description = ''
+            description = lib.mdDoc ''
               The domain name of the server, with optional explicit port.
               This is used by remote servers to connect to this server.
               This is also the last part of your UserID.
             '';
           };
           private_key = lib.mkOption {
-            type = lib.types.path;
-            example = "${workingDir}/matrix_key.pem";
-            description = ''
+            type = lib.types.either
+              lib.types.path
+              (lib.types.strMatching "^\\$CREDENTIALS_DIRECTORY/.+");
+            example = "$CREDENTIALS_DIRECTORY/private_key";
+            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 {
             type = lib.types.listOf lib.types.str;
             example = [ "matrix.org" ];
             default = [ "matrix.org" "vector.im" ];
-            description = ''
+            description = lib.mdDoc ''
               Lists of domains that the server will trust as identity
               servers to verify third party identifiers such as phone
               numbers and email addresses
@@ -114,7 +126,7 @@ in
           connection_string = lib.mkOption {
             type = lib.types.str;
             default = "file:federationapi.db";
-            description = ''
+            description = lib.mdDoc ''
               Database for the Appservice API.
             '';
           };
@@ -123,7 +135,7 @@ in
           registration_disabled = lib.mkOption {
             type = lib.types.bool;
             default = true;
-            description = ''
+            description = lib.mdDoc ''
               Whether to disable user registration to the server
               without the shared secret.
             '';
@@ -133,7 +145,7 @@ in
           connection_string = lib.mkOption {
             type = lib.types.str;
             default = "file:federationapi.db";
-            description = ''
+            description = lib.mdDoc ''
               Database for the Federation API.
             '';
           };
@@ -142,7 +154,7 @@ in
           connection_string = lib.mkOption {
             type = lib.types.str;
             default = "file:keyserver.db";
-            description = ''
+            description = lib.mdDoc ''
               Database for the Key Server (for end-to-end encryption).
             '';
           };
@@ -152,7 +164,7 @@ in
             connection_string = lib.mkOption {
               type = lib.types.str;
               default = "file:mediaapi.db";
-              description = ''
+              description = lib.mdDoc ''
                 Database for the Media API.
               '';
             };
@@ -160,7 +172,7 @@ in
           base_path = lib.mkOption {
             type = lib.types.str;
             default = "${workingDir}/media_store";
-            description = ''
+            description = lib.mdDoc ''
               Storage path for uploaded media.
             '';
           };
@@ -169,7 +181,7 @@ in
           connection_string = lib.mkOption {
             type = lib.types.str;
             default = "file:roomserver.db";
-            description = ''
+            description = lib.mdDoc ''
               Database for the Room Server.
             '';
           };
@@ -178,17 +190,36 @@ in
           connection_string = lib.mkOption {
             type = lib.types.str;
             default = "file:syncserver.db";
-            description = ''
+            description = lib.mdDoc ''
               Database for the Sync API.
             '';
           };
         };
+        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 {
               type = lib.types.str;
               default = "file:userapi_accounts.db";
-              description = ''
+              description = lib.mdDoc ''
                 Database for the User API, accounts.
               '';
             };
@@ -197,7 +228,7 @@ in
             connection_string = lib.mkOption {
               type = lib.types.str;
               default = "file:userapi_devices.db";
-              description = ''
+              description = lib.mdDoc ''
                 Database for the User API, devices.
               '';
             };
@@ -208,7 +239,7 @@ in
             connection_string = lib.mkOption {
               type = lib.types.str;
               default = "file:mscs.db";
-              description = ''
+              description = lib.mdDoc ''
                 Database for exerimental MSC's.
               '';
             };
@@ -216,12 +247,19 @@ in
         };
       };
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Configuration for dendrite, see:
-        <link xlink:href="https://github.com/matrix-org/dendrite/blob/master/dendrite-config.yaml"/>
+        <https://github.com/matrix-org/dendrite/blob/master/dendrite-config.yaml>
         for available options with which to populate settings.
       '';
     };
+    openRegistration = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Allow open registration without secondary verification (reCAPTCHA).
+      '';
+    };
   };
 
   config = lib.mkIf cfg.enable {
@@ -249,6 +287,7 @@ in
         RuntimeDirectoryMode = "0700";
         LimitNOFILE = 65535;
         EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+        LoadCredential = cfg.loadCredential;
         ExecStartPre = ''
           ${pkgs.envsubst}/bin/envsubst \
             -i ${configurationYaml} \
@@ -263,6 +302,8 @@ in
           "--https-bind-address :${builtins.toString cfg.httpsPort}"
           "--tls-cert ${cfg.tlsCert}"
           "--tls-key ${cfg.tlsKey}"
+        ] ++ lib.optionals cfg.openRegistration [
+          "--really-enable-open-registration"
         ]);
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         Restart = "on-failure";
diff --git a/nixos/modules/services/misc/mautrix-facebook.nix b/nixos/modules/services/matrix/mautrix-facebook.nix
index e046c791ac01..e74f25df764d 100644
--- a/nixos/modules/services/misc/mautrix-facebook.nix
+++ b/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,6 +25,7 @@ in {
         default = {
           homeserver = {
             address = "http://localhost:8008";
+            software = "standard";
           };
 
           appservice = rec {
@@ -44,6 +45,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}";
           };
@@ -75,15 +82,12 @@ in {
             };
           }
         '';
-        description = ''
-          <filename>config.yaml</filename> configuration as a Nix attribute set.
+        description = lib.mdDoc ''
+          {file}`config.yaml` configuration as a Nix attribute set.
           Configuration options should match those described in
-          <link xlink:href="https://github.com/mautrix/facebook/blob/master/mautrix_facebook/example-config.yaml">
-          example-config.yaml</link>.
-          </para>
+          [example-config.yaml](https://github.com/mautrix/facebook/blob/master/mautrix_facebook/example-config.yaml).
 
-          <para>
-          Secret tokens should be specified using <option>environmentFile</option>
+          Secret tokens should be specified using {option}`environmentFile`
           instead of this world-readable attribute set.
         '';
       };
@@ -91,34 +95,36 @@ in {
       environmentFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           File containing environment variables to be passed to the mautrix-telegram service.
 
-          Any config variable can be overridden by setting <literal>MAUTRIX_FACEBOOK_SOME_KEY</literal> to override the <literal>some.key</literal> variable.
+          Any config variable can be overridden by setting `MAUTRIX_FACEBOOK_SOME_KEY` to override the `some.key` variable.
         '';
       };
 
       configurePostgresql = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          Enable PostgreSQL and create a user and database for mautrix-facebook. The default <literal>settings</literal> reference this database, if you disable this option you must provide a database URL.
+        description = lib.mdDoc ''
+          Enable PostgreSQL and create a user and database for mautrix-facebook. The default `settings` reference this database, if you disable this option you must provide a database URL.
         '';
       };
 
       registrationData = mkOption {
         type = types.attrs;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Output data for appservice registration. Simply make any desired changes and serialize to JSON. Note that this data contains secrets so think twice before putting it into the nix store.
 
-          Currently <literal>as_token</literal> and <literal>hs_token</literal> need to be added as they are not known to this module.
+          Currently `as_token` and `hs_token` need to be added as they are not known to this module.
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
+    users.groups.mautrix-facebook = {};
+
     users.users.mautrix-facebook = {
       group = "mautrix-facebook";
       isSystemUser = true;
diff --git a/nixos/modules/services/misc/mautrix-telegram.nix b/nixos/modules/services/matrix/mautrix-telegram.nix
index 794c4dd9ddcd..5a632fd27e80 100644
--- a/nixos/modules/services/misc/mautrix-telegram.nix
+++ b/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 = {};
@@ -78,15 +82,12 @@ in {
             };
           }
         '';
-        description = ''
-          <filename>config.yaml</filename> configuration as a Nix attribute set.
+        description = lib.mdDoc ''
+          {file}`config.yaml` configuration as a Nix attribute set.
           Configuration options should match those described in
-          <link xlink:href="https://github.com/tulir/mautrix-telegram/blob/master/example-config.yaml">
-          example-config.yaml</link>.
-          </para>
+          [example-config.yaml](https://github.com/mautrix/telegram/blob/master/mautrix_telegram/example-config.yaml).
 
-          <para>
-          Secret tokens should be specified using <option>environmentFile</option>
+          Secret tokens should be specified using {option}`environmentFile`
           instead of this world-readable attribute set.
         '';
       };
@@ -94,14 +95,25 @@ in {
       environmentFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        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
-          <literal>MAUTRIX_TELEGRAM_APPSERVICE_AS_TOKEN</literal>,
-          <literal>MAUTRIX_TELEGRAM_APPSERVICE_HS_TOKEN</literal>,
-          <literal>MAUTRIX_TELEGRAM_TELEGRAM_API_ID</literal>,
-          <literal>MAUTRIX_TELEGRAM_TELEGRAM_API_HASH</literal> and optionally
-          <literal>MAUTRIX_TELEGRAM_TELEGRAM_BOT_TOKEN</literal>.
+          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"}`.
         '';
       };
 
@@ -111,7 +123,7 @@ in {
         defaultText = literalExpression ''
           optional config.services.matrix-synapse.enable "matrix-synapse.service"
         '';
-        description = ''
+        description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service.
         '';
       };
@@ -125,18 +137,21 @@ in {
       wantedBy = [ "multi-user.target" ];
       wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
       after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+      path = [ pkgs.lottieconverter ];
+
+      # 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 \
@@ -164,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 = ''
@@ -172,8 +187,6 @@ in {
             --config='${settingsFile}'
         '';
       };
-
-      restartTriggers = [ settingsFileUnsubstituted ];
     };
   };
 
diff --git a/nixos/modules/services/matrix/mjolnir.nix b/nixos/modules/services/matrix/mjolnir.nix
index 278924b05cf2..cbf7b93329d7 100644
--- a/nixos/modules/services/matrix/mjolnir.nix
+++ b/nixos/modules/services/matrix/mjolnir.nix
@@ -65,59 +65,59 @@ 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;
       default = "https://matrix.org";
-      description = ''
+      description = lib.mdDoc ''
         Where the homeserver is located (client-server URL).
 
-        If <literal>pantalaimon.enable</literal> is <literal>true</literal>, this option will become the homeserver to which <literal>pantalaimon</literal> connects.
-        The listen address of <literal>pantalaimon</literal> will then become the <literal>homeserverUrl</literal> of <literal>mjolnir</literal>.
+        If `pantalaimon.enable` is `true`, this option will become the homeserver to which `pantalaimon` connects.
+        The listen address of `pantalaimon` will then become the `homeserverUrl` of `mjolnir`.
       '';
     };
 
     accessTokenFile = mkOption {
       type = with types; nullOr path;
       default = null;
-      description = ''
-        File containing the matrix access token for the <literal>mjolnir</literal> user.
+      description = lib.mdDoc ''
+        File containing the matrix access token for the `mjolnir` user.
       '';
     };
 
     pantalaimon = mkOption {
-      description = ''
-        <literal>pantalaimon</literal> options (enables E2E Encryption support).
+      description = lib.mdDoc ''
+        `pantalaimon` options (enables E2E Encryption support).
 
-        This will create a <literal>pantalaimon</literal> instance with the name "mjolnir".
+        This will create a `pantalaimon` instance with the name "mjolnir".
       '';
       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;
-            description = "The username to login with.";
+            description = lib.mdDoc "The username to login with.";
           };
 
           passwordFile = mkOption {
             type = with types; nullOr path;
             default = null;
-            description = ''
-              File containing the matrix password for the <literal>mjolnir</literal> user.
+            description = lib.mdDoc ''
+              File containing the matrix password for the `mjolnir` user.
             '';
           };
 
           options = mkOption {
             type = types.submodule (import ./pantalaimon-options.nix);
             default = { };
-            description = ''
-              passthrough additional options to the <literal>pantalaimon</literal> service.
+            description = lib.mdDoc ''
+              passthrough additional options to the `pantalaimon` service.
             '';
           };
         };
@@ -127,7 +127,7 @@ in
     dataPath = mkOption {
       type = types.path;
       default = "/var/lib/mjolnir";
-      description = ''
+      description = lib.mdDoc ''
         The directory the bot should store various bits of information in.
       '';
     };
@@ -135,11 +135,11 @@ in
     managementRoom = mkOption {
       type = types.str;
       default = "#moderators:example.org";
-      description = ''
+      description = lib.mdDoc ''
         The room ID where people can use the bot. The bot has no access controls, so
         anyone in this room can use the bot - secure your room!
         This should be a room alias or room ID - not a matrix.to URL.
-        Note: <literal>mjolnir</literal> is fairly verbose - expect a lot of messages from it.
+        Note: `mjolnir` is fairly verbose - expect a lot of messages from it.
       '';
     };
 
@@ -152,7 +152,7 @@ in
           "https://matrix.to/#/#anotherroom:example.org"
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         A list of rooms to protect (matrix.to URLs).
       '';
     };
@@ -166,8 +166,8 @@ in
           automaticallyRedactForReasons = [ "spam" "advertising" ];
         }
       '';
-      description = ''
-        Additional settings (see <link xlink:href="https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml">mjolnir default config</link> for available settings). These settings will override settings made by the module config.
+      description = lib.mdDoc ''
+        Additional settings (see [mjolnir default config](https://github.com/matrix-org/mjolnir/blob/main/config/default.yaml) for available settings). These settings will override settings made by the module config.
       '';
     };
   };
diff --git a/nixos/modules/services/matrix/pantalaimon-options.nix b/nixos/modules/services/matrix/pantalaimon-options.nix
index 035c57540d09..3945a70fc86b 100644
--- a/nixos/modules/services/matrix/pantalaimon-options.nix
+++ b/nixos/modules/services/matrix/pantalaimon-options.nix
@@ -6,15 +6,15 @@ with lib;
     dataPath = mkOption {
       type = types.path;
       default = "/var/lib/pantalaimon-${name}";
-      description = ''
-        The directory where <literal>pantalaimon</literal> should store its state such as the database file.
+      description = lib.mdDoc ''
+        The directory where `pantalaimon` should store its state such as the database file.
       '';
     };
 
     logLevel = mkOption {
       type = types.enum [ "info" "warning" "error" "debug" ];
       default = "warning";
-      description = ''
+      description = lib.mdDoc ''
         Set the log level of the daemon.
       '';
     };
@@ -22,8 +22,8 @@ with lib;
     homeserver = mkOption {
       type = types.str;
       example = "https://matrix.org";
-      description = ''
-        The URI of the homeserver that the <literal>pantalaimon</literal> proxy should
+      description = lib.mdDoc ''
+        The URI of the homeserver that the `pantalaimon` proxy should
         forward requests to, without the matrix API path but including
         the http(s) schema.
       '';
@@ -32,7 +32,7 @@ with lib;
     ssl = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether or not SSL verification should be enabled for outgoing
         connections to the homeserver.
       '';
@@ -41,7 +41,7 @@ with lib;
     listenAddress = mkOption {
       type = types.str;
       default = "localhost";
-      description = ''
+      description = lib.mdDoc ''
         The address where the daemon will listen to client connections
         for this homeserver.
       '';
@@ -50,7 +50,7 @@ with lib;
     listenPort = mkOption {
       type = types.port;
       default = 8009;
-      description = ''
+      description = lib.mdDoc ''
         The port where the daemon will listen to client connections for
         this homeserver. Note that the listen address/port combination
         needs to be unique between different homeservers.
@@ -60,9 +60,9 @@ with lib;
     extraSettings = mkOption {
       type = types.attrs;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Extra configuration options. See
-        <link xlink:href="https://github.com/matrix-org/pantalaimon/blob/master/docs/man/pantalaimon.5.md">pantalaimon(5)</link>
+        [pantalaimon(5)](https://github.com/matrix-org/pantalaimon/blob/master/docs/man/pantalaimon.5.md)
         for available options.
       '';
     };
diff --git a/nixos/modules/services/matrix/pantalaimon.nix b/nixos/modules/services/matrix/pantalaimon.nix
index 63b40099ca5d..591ba9a7ab55 100644
--- a/nixos/modules/services/matrix/pantalaimon.nix
+++ b/nixos/modules/services/matrix/pantalaimon.nix
@@ -51,11 +51,11 @@ in
   options.services.pantalaimon-headless.instances = mkOption {
     default = { };
     type = types.attrsOf (types.submodule (import ./pantalaimon-options.nix));
-    description = ''
+    description = lib.mdDoc ''
       Declarative instance config.
 
       Note: to use pantalaimon interactively, e.g. for a Matrix client which does not
-      support End-to-end encryption (like <literal>fractal</literal>), refer to the home-manager module.
+      support End-to-end encryption (like `fractal`), refer to the home-manager module.
     '';
   };
 
diff --git a/nixos/modules/services/matrix/matrix-synapse-log_config.yaml b/nixos/modules/services/matrix/synapse-log_config.yaml
index d85bdd1208f9..d85bdd1208f9 100644
--- a/nixos/modules/services/matrix/matrix-synapse-log_config.yaml
+++ b/nixos/modules/services/matrix/synapse-log_config.yaml
diff --git a/nixos/modules/services/matrix/matrix-synapse.nix b/nixos/modules/services/matrix/synapse.nix
index a498aff7a55b..b9b581acb34a 100644
--- a/nixos/modules/services/matrix/matrix-synapse.nix
+++ b/nixos/modules/services/matrix/synapse.nix
@@ -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,12 +138,12 @@ in {
 
   options = {
     services.matrix-synapse = {
-      enable = mkEnableOption "matrix.org synapse";
+      enable = mkEnableOption (lib.mdDoc "matrix.org synapse");
 
       configFile = mkOption {
         type = types.path;
         readOnly = true;
-        description = ''
+        description = lib.mdDoc ''
           Path to the configuration file on the target system. Useful to configure e.g. workers
           that also need this.
         '';
@@ -153,7 +153,7 @@ in {
         type = types.package;
         default = pkgs.matrix-synapse;
         defaultText = literalExpression "pkgs.matrix-synapse";
-        description = ''
+        description = lib.mdDoc ''
           Overridable attribute of the matrix synapse server package to use.
         '';
       };
@@ -167,7 +167,7 @@ in {
             matrix-synapse-pam
           ];
         '';
-        description = ''
+        description = lib.mdDoc ''
           List of additional Matrix plugins to make available.
         '';
       };
@@ -175,7 +175,7 @@ in {
       withJemalloc = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to preload jemalloc to reduce memory fragmentation and overall usage.
         '';
       };
@@ -183,7 +183,7 @@ in {
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/matrix-synapse";
-        description = ''
+        description = lib.mdDoc ''
           The directory where matrix-synapse stores its stateful data such as
           certificates, media and uploads.
         '';
@@ -191,12 +191,12 @@ in {
 
       settings = mkOption {
         default = {};
-        description = ''
+        description = mdDoc ''
           The primary synapse configuration. See the
-          <link xlink:href="https://github.com/matrix-org/synapse/blob/v${cfg.package.version}/docs/sample_config.yaml">sample configuration</link>
+          [sample configuration](https://github.com/matrix-org/synapse/blob/v${cfg.package.version}/docs/sample_config.yaml)
           for possible values.
 
-          Secrets should be passed in by using the <literal>extraConfigFiles</literal> option.
+          Secrets should be passed in by using the `extraConfigFiles` option.
         '';
         type = with types; submodule {
           freeformType = format.type;
@@ -210,7 +210,7 @@ in {
               example = "example.com";
               default = config.networking.hostName;
               defaultText = literalExpression "config.networking.hostName";
-              description = ''
+              description = lib.mdDoc ''
                 The domain name of the server, with optional explicit port.
                 This is used by remote servers to look up the server address.
                 This is also the last part of your UserID.
@@ -222,7 +222,7 @@ in {
             enable_registration = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Enable registration for new users.
               '';
             };
@@ -230,30 +230,30 @@ in {
             registration_shared_secret = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              description = mdDoc ''
                 If set, allows registration by anyone who also has the shared
                 secret, even if registration is otherwise disabled.
 
-                Secrets should be passed in via <literal>extraConfigFiles</literal>!
+                Secrets should be passed in via `extraConfigFiles`!
               '';
             };
 
             macaroon_secret_key = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              description = mdDoc ''
                 Secret key for authentication tokens. If none is specified,
                 the registration_shared_secret is used, if one is given; otherwise,
                 a secret key is derived from the signing key.
 
-                Secrets should be passed in via <literal>extraConfigFiles</literal>!
+                Secrets should be passed in via `extraConfigFiles`!
               '';
             };
 
             enable_metrics = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Enable collection and rendering of performance metrics
               '';
             };
@@ -261,7 +261,7 @@ in {
             report_stats = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether or not to report anonymized homeserver usage statistics.
               '';
             };
@@ -269,7 +269,7 @@ in {
             signing_key_path = mkOption {
               type = types.path;
               default = "${cfg.dataDir}/homeserver.signing.key";
-              description = ''
+              description = lib.mdDoc ''
                 Path to the signing key to sign messages with.
               '';
             };
@@ -278,15 +278,16 @@ in {
               type = types.path;
               default = "/run/matrix-synapse.pid";
               readOnly = true;
-              description = ''
+              description = lib.mdDoc ''
                 The file to store the PID in.
               '';
             };
 
             log_config = mkOption {
               type = types.path;
-              default = ./matrix-synapse-log_config.yaml;
-              description = ''
+              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.
               '';
             };
@@ -296,7 +297,8 @@ in {
               default = if lib.versionAtLeast config.system.stateVersion "22.05"
                 then "${cfg.dataDir}/media_store"
                 else "${cfg.dataDir}/media";
-              description = ''
+              defaultText = "${cfg.dataDir}/media_store for when system.stateVersion is at least 22.05, ${cfg.dataDir}/media when lower than 22.05";
+              description = lib.mdDoc ''
                 Directory where uploaded images and attachments are stored.
               '';
             };
@@ -305,7 +307,7 @@ in {
               type = types.nullOr types.str;
               default = null;
               example = "https://example.com:8448/";
-              description = ''
+              description = lib.mdDoc ''
                 The public-facing base URL for the client API (not including _matrix/...)
               '';
             };
@@ -314,7 +316,7 @@ in {
               type = types.nullOr types.str;
               default = null;
               example = "/var/lib/acme/example.com/fullchain.pem";
-              description = ''
+              description = lib.mdDoc ''
                 PEM encoded X509 certificate for TLS.
                 You can replace the self-signed certificate that synapse
                 autogenerates on launch with your own SSL certificate + key pair
@@ -327,7 +329,7 @@ in {
               type = types.nullOr types.str;
               default = null;
               example = "/var/lib/acme/example.com/key.pem";
-              description = ''
+              description = lib.mdDoc ''
                 PEM encoded private key for TLS. Specify null if synapse is not
                 speaking TLS directly.
               '';
@@ -337,7 +339,7 @@ in {
               type = types.bool;
               default = true;
               example = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to enable presence tracking.
 
                 Presence tracking allows users to see the state (e.g online/offline)
@@ -351,7 +353,7 @@ in {
                   port = mkOption {
                     type = types.port;
                     example = 8448;
-                    description = ''
+                    description = lib.mdDoc ''
                       The port to listen for HTTP(S) requests on.
                     '';
                   };
@@ -368,7 +370,7 @@ in {
                       "0.0.0.0"
                     ]
                     '';
-                    description = ''
+                    description = lib.mdDoc ''
                      IP addresses to bind the listener to.
                     '';
                   };
@@ -382,7 +384,7 @@ in {
                     ];
                     default = "http";
                     example = "metrics";
-                    description = ''
+                    description = lib.mdDoc ''
                       The type of the listener, usually http.
                     '';
                   };
@@ -391,7 +393,7 @@ in {
                     type = types.bool;
                     default = true;
                     example = false;
-                    description = ''
+                    description = lib.mdDoc ''
                       Whether to enable TLS on the listener socket.
                     '';
                   };
@@ -400,7 +402,7 @@ in {
                     type = types.bool;
                     default = false;
                     example = true;
-                    description = ''
+                    description = lib.mdDoc ''
                       Use the X-Forwarded-For (XFF) header as the client IP and not the
                       actual client IP.
                     '';
@@ -421,7 +423,7 @@ in {
                             "replication"
                             "static"
                           ]);
-                          description = ''
+                          description = lib.mdDoc ''
                             List of resources to host on this listener.
                           '';
                           example = [
@@ -430,7 +432,7 @@ in {
                         };
                         compress = mkOption {
                           type = types.bool;
-                          description = ''
+                          description = lib.mdDoc ''
                             Should synapse compress HTTP responses to clients that support it?
                             This should be disabled if running synapse behind a load balancer
                             that can do automatic compression.
@@ -438,7 +440,7 @@ in {
                         };
                       };
                     });
-                    description = ''
+                    description = lib.mdDoc ''
                       List of HTTP resources to serve on this listener.
                     '';
                   };
@@ -458,7 +460,7 @@ in {
                   compress = false;
                 } ];
               } ];
-              description = ''
+              description = lib.mdDoc ''
                 List of ports that Synapse should listen on, their purpose and their configuration.
               '';
             };
@@ -476,7 +478,7 @@ in {
                 then "psycopg2"
                 else "sqlite3"
               '';
-              description = ''
+              description = lib.mdDoc ''
                 The database engine name. Can be sqlite3 or psycopg2.
               '';
             };
@@ -493,7 +495,7 @@ in {
                 psycopg2 = "matrix-synapse";
               }.''${${options.services.matrix-synapse.settings}.database.name};
               '';
-              description = ''
+              description = lib.mdDoc ''
                 Name of the database when using the psycopg2 backend,
                 path to the database location when using sqlite3.
               '';
@@ -505,7 +507,7 @@ in {
                 sqlite3 = null;
                 psycopg2 = "matrix-synapse";
               }.${cfg.settings.database.name};
-              description = ''
+              description = lib.mdDoc ''
                 Username to connect with psycopg2, set to null
                 when using sqlite3.
               '';
@@ -515,7 +517,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.
@@ -545,7 +547,7 @@ in {
                 "fec0::/10"
                 "ff00::/8"
               ];
-              description = ''
+              description = lib.mdDoc ''
                 List of IP address CIDR ranges that the URL preview spider is denied
                 from accessing.
               '';
@@ -554,7 +556,7 @@ in {
             url_preview_ip_range_whitelist = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = ''
+              description = lib.mdDoc ''
                 List of IP address CIDR ranges that the URL preview spider is allowed
                 to access even if they are specified in url_preview_ip_range_blacklist.
               '';
@@ -563,7 +565,7 @@ in {
             url_preview_url_blacklist = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = ''
+              description = lib.mdDoc ''
                 Optional list of URL matches that the URL preview spider is
                 denied from accessing.
               '';
@@ -573,7 +575,7 @@ in {
               type = types.str;
               default = "50M";
               example = "100M";
-              description = ''
+              description = lib.mdDoc ''
                 The largest allowed upload size in bytes
               '';
             };
@@ -582,7 +584,7 @@ in {
               type = types.str;
               default = "32M";
               example = "64M";
-              description = ''
+              description = lib.mdDoc ''
                 Maximum number of pixels that will be thumbnailed
               '';
             };
@@ -591,7 +593,7 @@ in {
               type = types.bool;
               default = false;
               example = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to generate new thumbnails on the fly to precisely match
                 the resolution requested by the client. If true then whenever
                 a new resolution is requested by the client the server will
@@ -609,7 +611,7 @@ in {
                 "turns:turn.example.com:5349?transport=udp"
                 "turns:turn.example.com:5349?transport=tcp"
               ];
-              description = ''
+              description = lib.mdDoc ''
                 The public URIs of the TURN server to give to clients
               '';
             };
@@ -619,10 +621,10 @@ in {
               example = literalExpression ''
                 config.services.coturn.static-auth-secret
               '';
-              description = ''
+              description = mdDoc ''
                 The shared secret used to compute passwords for the TURN server.
 
-                Secrets should be passed in via <literal>extraConfigFiles</literal>!
+                Secrets should be passed in via `extraConfigFiles`!
               '';
             };
 
@@ -632,7 +634,7 @@ in {
                   server_name = mkOption {
                     type = types.str;
                     example = "matrix.org";
-                    description = ''
+                    description = lib.mdDoc ''
                       Hostname of the trusted server.
                     '';
                   };
@@ -645,7 +647,7 @@ in {
                         "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
                       }
                     '';
-                    description = ''
+                    description = lib.mdDoc ''
                       Attribute set from key id to base64 encoded public key.
 
                       If specified synapse will check that the response is signed
@@ -660,7 +662,7 @@ in {
                   "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
                 };
               } ];
-              description = ''
+              description = lib.mdDoc ''
                 The trusted servers to download signing keys from.
               '';
             };
@@ -668,7 +670,7 @@ in {
             app_service_config_files = mkOption {
               type = types.listOf types.path;
               default = [ ];
-              description = ''
+              description = lib.mdDoc ''
                 A list of application service config file to use
               '';
             };
@@ -680,7 +682,7 @@ in {
       extraConfigFiles = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Extra config files to include.
 
           The configuration files will be included based on the command line
@@ -758,6 +760,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" ];
       };
     };
 
@@ -766,7 +795,7 @@ in {
 
   meta = {
     buildDocsInSandbox = false;
-    doc = ./matrix-synapse.xml;
+    doc = ./synapse.xml;
     maintainers = teams.matrix.members;
   };
 
diff --git a/nixos/modules/services/matrix/matrix-synapse.xml b/nixos/modules/services/matrix/synapse.xml
index cf33957d58ec..40ad72173a53 100644
--- a/nixos/modules/services/matrix/matrix-synapse.xml
+++ b/nixos/modules/services/matrix/synapse.xml
@@ -30,24 +30,29 @@
    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">
+   <link xlink:href="https://matrix-org.github.io/synapse/latest/setup/installation.html">
    installation instructions of Synapse </link>.
 <programlisting>
-{ pkgs, lib, ... }:
+{ pkgs, lib, config, ... }:
 let
-  fqdn =
-    let
-      join = hostName: domain: hostName + lib.optionalString (domain != null) ".${domain}";
-    in join config.networking.hostName config.networking.domain;
-in {
-  networking = {
-    <link linkend="opt-networking.hostName">hostName</link> = "myhostname";
-    <link linkend="opt-networking.domain">domain</link> = "example.org";
+  fqdn = "${config.networking.hostName}.${config.networking.domain}";
+  clientConfig = {
+    "m.homeserver".base_url = "https://${fqdn}";
+    "m.identity_server" = {};
   };
-  <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
+  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 ];
 
-  <link linkend="opt-services.postgresql.enable">services.postgresql.enable</link> = true;
-  <link linkend="opt-services.postgresql.initialScript">services.postgresql.initialScript</link> = pkgs.writeText "synapse-init.sql" ''
+  <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
@@ -57,78 +62,41 @@ in {
 
   services.nginx = {
     <link linkend="opt-services.nginx.enable">enable</link> = true;
-    # only recommendedProxySettings and recommendedGzipSettings 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;
-
     <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
-      # This host section can be placed on a different host than the rest,
-      # i.e. to delegate from the host being accessible as ${config.networking.domain}
-      # to another host actually running the Matrix homeserver.
-      "${config.networking.domain}" = {
+      "${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> =
-          let
-            # use 443 instead of the default 8448 port to unite
-            # the client-server and server-server port for simplicity
-            server = { "m.server" = "${fqdn}:443"; };
-          in ''
-            add_header Content-Type application/json;
-            return 200 '${builtins.toJSON server}';
-          '';
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> =
-          let
-            client = {
-              "m.homeserver" =  { "base_url" = "https://${fqdn}"; };
-              "m.identity_server" =  { "base_url" = "https://vector.im"; };
-            };
-          # ACAO required to allow element-web on any URL to request this json file
-          in ''
-            add_header Content-Type application/json;
-            add_header Access-Control-Allow-Origin *;
-            return 200 '${builtins.toJSON client}';
-          '';
+        <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' />
       };
-
-      # Reverse proxy for Matrix client-server and server-server communication
-      ${fqdn} = {
+      "${fqdn}" = {
         <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
         <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
-
-        # Or do a redirect instead of the 404, or whatever is appropriate for you.
-        # But do not put a Matrix Web client here! See the Element web section below.
-        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = ''
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = '' <co xml:id='ex-matrix-synapse-rev-default' />
           return 404;
         '';
-
-        # forward all Matrix API calls to the synapse Matrix homeserver
-        locations."/_matrix" = {
-          <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">proxyPass</link> = "http://[::1]:8008"; # without a trailing /
-        };
+        <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">server_name</link> = config.networking.domain;
-    <link linkend="opt-services.matrix-synapse.settings.listeners">listeners</link> = [
-      {
-        <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008;
+    <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" ];
+          <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;
-        } {
-          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.names">names</link> = [ "federation" ];
-          <link linkend="opt-services.matrix-synapse.settings.listeners._.resources._.compress">compress</link> = false;
         } ];
       }
     ];
@@ -136,20 +104,59 @@ in {
 }
 </programlisting>
   </para>
-
-  <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>
-
+  <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> =
@@ -159,7 +166,7 @@ in {
    To create a new user or admin, run the following after you have set the secret
    and have rebuilt NixOS:
 <screen>
-<prompt>$ </prompt>nix run nixpkgs.matrix-synapse
+<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>
@@ -168,12 +175,51 @@ in {
 Success!
 </screen>
    In the example, this would create a user with the Matrix Identifier
-   <literal>@your-username:example.org</literal>. Note that the registration
-   secret ends up in the nix store and therefore is world-readable by any user
-   on your machine, so it makes sense to only temporarily activate the
-   <link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">registration_shared_secret</link>
-   option until a better solution for NixOS is in place.
+   <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>
@@ -206,10 +252,7 @@ Success!
 
     <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.element-web.override {
       conf = {
-        default_server_config."m.homeserver" = {
-          "base_url" = "https://${fqdn}";
-          "server_name" = "${fqdn}";
-        };
+        default_server_config = clientConfig; # see `clientConfig` from the snippet above.
       };
     };
   };
@@ -217,15 +260,17 @@ Success!
 </programlisting>
   </para>
 
-  <para>
-   Note that the Element developers do not recommend running Element and your Matrix
-   homeserver on the same fully-qualified domain name for security reasons. In
-   the example, this means that you should not reuse the
-   <literal>myhostname.example.org</literal> virtualHost to also serve 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/riot-web#important-security-note">Element
-   Important Security Notes</link> for more information on this subject.
-  </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/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 2b9c6d80abbd..b8e9dcaf4663 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -9,18 +9,18 @@ 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;
         default = "airsonic";
-        description = "User account under which airsonic runs.";
+        description = lib.mdDoc "User account under which airsonic runs.";
       };
 
       home = mkOption {
         type = types.path;
         default = "/var/lib/airsonic";
-        description = ''
+        description = lib.mdDoc ''
           The directory where Airsonic will create files.
           Make sure it is writable.
         '';
@@ -29,7 +29,7 @@ in {
       virtualHost = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
         '';
       };
@@ -37,7 +37,7 @@ in {
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           The host name or IP address on which to bind Airsonic.
           The default value is appropriate for first launch, when the
           default credentials are easy to guess. It is also appropriate
@@ -48,9 +48,9 @@ in {
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 4040;
-        description = ''
+        description = lib.mdDoc ''
           The port on which Airsonic will listen for
           incoming HTTP traffic. Set to 0 to disable.
         '';
@@ -59,7 +59,7 @@ in {
       contextPath = mkOption {
         type = types.path;
         default = "/";
-        description = ''
+        description = lib.mdDoc ''
           The context path, i.e., the last part of the Airsonic
           URL. Typically '/' or '/airsonic'. Default '/'
         '';
@@ -68,7 +68,7 @@ in {
       maxMemory = mkOption {
         type = types.int;
         default = 100;
-        description = ''
+        description = lib.mdDoc ''
           The memory limit (max Java heap size) in megabytes.
           Default: 100
         '';
@@ -78,7 +78,7 @@ in {
         type = types.listOf types.path;
         default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
         defaultText = literalExpression ''[ "''${pkgs.ffmpeg.bin}/bin/ffmpeg" ]'';
-        description = ''
+        description = lib.mdDoc ''
           List of paths to transcoder executables that should be accessible
           from Airsonic. Symlinks will be created to each executable inside
           ''${config.${opt.home}}/transcoders.
@@ -89,7 +89,7 @@ in {
         type = types.package;
         default = pkgs.jre8;
         defaultText = literalExpression "pkgs.jre8";
-        description = ''
+        description = lib.mdDoc ''
           JRE package to use.
 
           Airsonic only supports Java 8, airsonic-advanced requires at least
@@ -101,11 +101,11 @@ in {
         type = types.path;
         default = "${pkgs.airsonic}/webapps/airsonic.war";
         defaultText = literalExpression ''"''${pkgs.airsonic}/webapps/airsonic.war"'';
-        description = "Airsonic war file to use.";
+        description = lib.mdDoc "Airsonic war file to use.";
       };
 
       jvmOptions = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Extra command line options for the JVM running AirSonic.
           Useful for sending jukebox output to non-default alsa
           devices.
diff --git a/nixos/modules/services/misc/ananicy.nix b/nixos/modules/services/misc/ananicy.nix
index 191666bc3625..d2287fba6afc 100644
--- a/nixos/modules/services/misc/ananicy.nix
+++ b/nixos/modules/services/misc/ananicy.nix
@@ -11,14 +11,14 @@ 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;
         default = pkgs.ananicy;
         defaultText = literalExpression "pkgs.ananicy";
         example = literalExpression "pkgs.ananicy-cpp";
-        description = ''
+        description = lib.mdDoc ''
           Which ananicy package to use.
         '';
       };
@@ -29,18 +29,18 @@ in
         example = {
           apply_nice = false;
         };
-        description = ''
-          See <link xlink:href="https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf"/>
+        description = lib.mdDoc ''
+          See <https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf>
         '';
       };
 
       extraRules = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra rules in json format on separate lines. See:
-          <link xlink:href="https://github.com/Nefelim4ag/Ananicy#configuration"/>
-          <link xlink:href="https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration"/>
+          <https://github.com/Nefelim4ag/Ananicy#configuration>
+          <https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration>
         '';
         example = literalExpression ''
           '''
diff --git a/nixos/modules/services/misc/ankisyncd.nix b/nixos/modules/services/misc/ankisyncd.nix
index 69e471f4f577..5198b8242023 100644
--- a/nixos/modules/services/misc/ankisyncd.nix
+++ b/nixos/modules/services/misc/ankisyncd.nix
@@ -28,31 +28,31 @@ let
 in
   {
     options.services.ankisyncd = {
-      enable = mkEnableOption "ankisyncd";
+      enable = mkEnableOption (lib.mdDoc "ankisyncd");
 
       package = mkOption {
         type = types.package;
         default = pkgs.ankisyncd;
         defaultText = literalExpression "pkgs.ankisyncd";
-        description = "The package to use for the ankisyncd command.";
+        description = lib.mdDoc "The package to use for the ankisyncd command.";
       };
 
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "ankisyncd host";
+        description = lib.mdDoc "ankisyncd host";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 27701;
-        description = "ankisyncd port";
+        description = lib.mdDoc "ankisyncd port";
       };
 
       openFirewall = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to open the firewall for the specified port.";
+        description = lib.mdDoc "Whether to open the firewall for the specified port.";
       };
     };
 
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index d1856fff4aa4..598907aaf1c6 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -26,49 +26,49 @@ in {
 
   options.services.apache-kafka = {
     enable = mkOption {
-      description = "Whether to enable Apache Kafka.";
+      description = lib.mdDoc "Whether to enable Apache Kafka.";
       default = false;
       type = types.bool;
     };
 
     brokerId = mkOption {
-      description = "Broker ID.";
+      description = lib.mdDoc "Broker ID.";
       default = -1;
       type = types.int;
     };
 
     port = mkOption {
-      description = "Port number the broker should listen on.";
+      description = lib.mdDoc "Port number the broker should listen on.";
       default = 9092;
-      type = types.int;
+      type = types.port;
     };
 
     hostname = mkOption {
-      description = "Hostname the broker should bind to.";
+      description = lib.mdDoc "Hostname the broker should bind to.";
       default = "localhost";
       type = types.str;
     };
 
     logDirs = mkOption {
-      description = "Log file directories";
+      description = lib.mdDoc "Log file directories";
       default = [ "/tmp/kafka-logs" ];
       type = types.listOf types.path;
     };
 
     zookeeper = mkOption {
-      description = "Zookeeper connection string";
+      description = lib.mdDoc "Zookeeper connection string";
       default = "localhost:2181";
       type = types.str;
     };
 
     extraProperties = mkOption {
-      description = "Extra properties for server.properties.";
+      description = lib.mdDoc "Extra properties for server.properties.";
       type = types.nullOr types.lines;
       default = null;
     };
 
     serverProperties = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Complete server.properties content. Other server.properties config
         options will be ignored if this option is used.
       '';
@@ -77,7 +77,7 @@ in {
     };
 
     log4jProperties = mkOption {
-      description = "Kafka log4j property configuration.";
+      description = lib.mdDoc "Kafka log4j property configuration.";
       default = ''
         log4j.rootLogger=INFO, stdout
 
@@ -89,7 +89,7 @@ in {
     };
 
     jvmOptions = mkOption {
-      description = "Extra command line options for the JVM running Kafka.";
+      description = lib.mdDoc "Extra command line options for the JVM running Kafka.";
       default = [];
       type = types.listOf types.str;
       example = [
@@ -100,14 +100,14 @@ in {
     };
 
     package = mkOption {
-      description = "The kafka package to use";
+      description = lib.mdDoc "The kafka package to use";
       default = pkgs.apacheKafka;
       defaultText = literalExpression "pkgs.apacheKafka";
       type = types.package;
     };
 
     jre = mkOption {
-      description = "The JRE with which to run Kafka";
+      description = lib.mdDoc "The JRE with which to run Kafka";
       default = cfg.package.passthru.jre;
       defaultText = literalExpression "pkgs.apacheKafka.passthru.jre";
       type = types.package;
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
new file mode 100644
index 000000000000..c94852e3aad9
--- /dev/null
+++ b/nixos/modules/services/misc/atuin.nix
@@ -0,0 +1,85 @@
+{ 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.";
+      };
+
+      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.";
+      };
+
+    };
+  };
+
+  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";
+      after = [ "network.target" "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_OPEN_REGISTRATION = boolToString cfg.openRegistration;
+        ATUIN_DB_URI = "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/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix
index 5fce990afece..55ab15ff003d 100644
--- a/nixos/modules/services/misc/autofs.nix
+++ b/nixos/modules/services/misc/autofs.nix
@@ -21,7 +21,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Mount filesystems on demand. Unmount them automatically.
           You may also be interested in afuse.
         '';
@@ -46,21 +46,21 @@ in
             /auto file:''${mapConf}
           '''
         '';
-        description = ''
-          Contents of <literal>/etc/auto.master</literal> file. See <command>auto.master(5)</command> and <command>autofs(5)</command>.
+        description = lib.mdDoc ''
+          Contents of `/etc/auto.master` file. See {command}`auto.master(5)` and {command}`autofs(5)`.
         '';
       };
 
       timeout = mkOption {
         type = types.int;
         default = 600;
-        description = "Set the global minimum timeout, in seconds, until directories are unmounted";
+        description = lib.mdDoc "Set the global minimum timeout, in seconds, until directories are unmounted";
       };
 
       debug = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Pass -d and -7 to automount and write log to the system journal.
         '';
       };
diff --git a/nixos/modules/services/misc/autorandr.nix b/nixos/modules/services/misc/autorandr.nix
index ef799e9ce3b6..072064143dbd 100644
--- a/nixos/modules/services/misc/autorandr.nix
+++ b/nixos/modules/services/misc/autorandr.nix
@@ -27,22 +27,22 @@ let
     options = {
       fingerprint = mkOption {
         type = types.attrsOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Output name to EDID mapping.
-          Use <code>autorandr --fingerprint</code> to get current setup values.
+          Use `autorandr --fingerprint` to get current setup values.
         '';
         default = { };
       };
 
       config = mkOption {
         type = types.attrsOf configModule;
-        description = "Per output profile configuration.";
+        description = lib.mdDoc "Per output profile configuration.";
         default = { };
       };
 
       hooks = mkOption {
         type = hooksModule;
-        description = "Profile hook scripts.";
+        description = lib.mdDoc "Profile hook scripts.";
         default = { };
       };
     };
@@ -52,54 +52,54 @@ let
     options = {
       enable = mkOption {
         type = types.bool;
-        description = "Whether to enable the output.";
+        description = lib.mdDoc "Whether to enable the output.";
         default = true;
       };
 
       crtc = mkOption {
         type = types.nullOr types.ints.unsigned;
-        description = "Output video display controller.";
+        description = lib.mdDoc "Output video display controller.";
         default = null;
         example = 0;
       };
 
       primary = mkOption {
         type = types.bool;
-        description = "Whether output should be marked as primary";
+        description = lib.mdDoc "Whether output should be marked as primary";
         default = false;
       };
 
       position = mkOption {
         type = types.str;
-        description = "Output position";
+        description = lib.mdDoc "Output position";
         default = "";
         example = "5760x0";
       };
 
       mode = mkOption {
         type = types.str;
-        description = "Output resolution.";
+        description = lib.mdDoc "Output resolution.";
         default = "";
         example = "3840x2160";
       };
 
       rate = mkOption {
         type = types.str;
-        description = "Output framerate.";
+        description = lib.mdDoc "Output framerate.";
         default = "";
         example = "60.00";
       };
 
       gamma = mkOption {
         type = types.str;
-        description = "Output gamma configuration.";
+        description = lib.mdDoc "Output gamma configuration.";
         default = "";
         example = "1.0:0.909:0.833";
       };
 
       rotate = mkOption {
         type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]);
-        description = "Output rotate configuration.";
+        description = lib.mdDoc "Output rotate configuration.";
         default = null;
         example = "left";
       };
@@ -114,19 +114,16 @@ let
             [ 0.0 0.0 1.0 ]
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Refer to
-          <citerefentry>
-            <refentrytitle>xrandr</refentrytitle>
-            <manvolnum>1</manvolnum>
-          </citerefentry>
+          {manpage}`xrandr(1)`
           for the documentation of the transform matrix.
         '';
       };
 
       dpi = mkOption {
         type = types.nullOr types.ints.positive;
-        description = "Output DPI configuration.";
+        description = lib.mdDoc "Output DPI configuration.";
         default = null;
         example = 96;
       };
@@ -136,36 +133,33 @@ let
           options = {
             method = mkOption {
               type = types.enum [ "factor" "pixel" ];
-              description = "Output scaling method.";
+              description = lib.mdDoc "Output scaling method.";
               default = "factor";
               example = "pixel";
             };
 
             x = mkOption {
               type = types.either types.float types.ints.positive;
-              description = "Horizontal scaling factor/pixels.";
+              description = lib.mdDoc "Horizontal scaling factor/pixels.";
             };
 
             y = mkOption {
               type = types.either types.float types.ints.positive;
-              description = "Vertical scaling factor/pixels.";
+              description = lib.mdDoc "Vertical scaling factor/pixels.";
             };
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           Output scale configuration.
-          </para><para>
+
           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.
-          </para><para>
+
           This option is a shortcut version of the transform option and they are mutually
           exclusive.
         '';
@@ -184,19 +178,19 @@ let
     options = {
       postswitch = mkOption {
         type = types.attrsOf hookType;
-        description = "Postswitch hook executed after mode switch.";
+        description = lib.mdDoc "Postswitch hook executed after mode switch.";
         default = { };
       };
 
       preswitch = mkOption {
         type = types.attrsOf hookType;
-        description = "Preswitch hook executed before mode switch.";
+        description = lib.mdDoc "Preswitch hook executed before mode switch.";
         default = { };
       };
 
       predetect = mkOption {
         type = types.attrsOf hookType;
-        description = ''
+        description = lib.mdDoc ''
           Predetect hook executed before autorandr attempts to run xrandr.
         '';
         default = { };
@@ -248,12 +242,12 @@ 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";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Fallback if no monitor layout can be detected. See the docs
           (https://github.com/phillipberndt/autorandr/blob/v1.0/README.md#how-to-use)
           for further reference.
@@ -262,9 +256,9 @@ in {
 
       hooks = mkOption {
         type = hooksModule;
-        description = "Global hook scripts";
+        description = lib.mdDoc "Global hook scripts";
         default = { };
-        example = ''
+        example = literalExpression ''
           {
             postswitch = {
               "notify-i3" = "''${pkgs.i3}/bin/i3-msg restart";
@@ -285,14 +279,14 @@ in {
                     exit 1
                 esac
                 echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge
-              '''
+              ''';
             };
           }
         '';
       };
       profiles = mkOption {
         type = types.attrsOf profileModule;
-        description = "Autorandr profiles specification.";
+        description = lib.mdDoc "Autorandr profiles specification.";
         default = { };
         example = literalExpression ''
           {
diff --git a/nixos/modules/services/misc/bazarr.nix b/nixos/modules/services/misc/bazarr.nix
index 99343a146a7a..07c935053591 100644
--- a/nixos/modules/services/misc/bazarr.nix
+++ b/nixos/modules/services/misc/bazarr.nix
@@ -8,30 +8,30 @@ 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;
         default = false;
-        description = "Open ports in the firewall for the bazarr web interface.";
+        description = lib.mdDoc "Open ports in the firewall for the bazarr web interface.";
       };
 
       listenPort = mkOption {
         type = types.port;
         default = 6767;
-        description = "Port on which the bazarr web interface should listen";
+        description = lib.mdDoc "Port on which the bazarr web interface should listen";
       };
 
       user = mkOption {
         type = types.str;
         default = "bazarr";
-        description = "User account under which bazarr runs.";
+        description = lib.mdDoc "User account under which bazarr runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "bazarr";
-        description = "Group under which bazarr runs.";
+        description = lib.mdDoc "Group under which bazarr runs.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/beanstalkd.nix b/nixos/modules/services/misc/beanstalkd.nix
index 1c674a5b23bf..4262cae323b9 100644
--- a/nixos/modules/services/misc/beanstalkd.nix
+++ b/nixos/modules/services/misc/beanstalkd.nix
@@ -12,18 +12,18 @@ in
 
   options = {
     services.beanstalkd = {
-      enable = mkEnableOption "the Beanstalk work queue";
+      enable = mkEnableOption (lib.mdDoc "the Beanstalk work queue");
 
       listen = {
         port = mkOption {
-          type = types.int;
-          description = "TCP port that will be used to accept client connections.";
+          type = types.port;
+          description = lib.mdDoc "TCP port that will be used to accept client connections.";
           default = 11300;
         };
 
         address = mkOption {
           type = types.str;
-          description = "IP address to listen on.";
+          description = lib.mdDoc "IP address to listen on.";
           default = "127.0.0.1";
           example = "0.0.0.0";
         };
@@ -32,7 +32,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to open ports in the firewall for the server.";
+        description = lib.mdDoc "Whether to open ports in the firewall for the server.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/bees.nix b/nixos/modules/services/misc/bees.nix
index fa00d7e4f55d..37f90c682221 100644
--- a/nixos/modules/services/misc/bees.nix
+++ b/nixos/modules/services/misc/bees.nix
@@ -11,14 +11,13 @@ let
   fsOptions = with types; {
     options.spec = mkOption {
       type = str;
-      description = ''
+      description = lib.mdDoc ''
         Description of how to identify the filesystem to be duplicated by this
         instance of bees. Note that deduplication crosses subvolumes; one must
         not configure multiple instances for subvolumes of the same filesystem
         (or block devices which are part of the same filesystem), but only for
         completely independent btrfs filesystems.
-        </para>
-        <para>
+
         This must be in a format usable by findmnt; that could be a key=value
         pair, or a bare path to a mount point.
         Using bare paths will allow systemd to start the beesd service only
@@ -29,14 +28,12 @@ let
     options.hashTableSizeMB = mkOption {
       type = types.addCheck types.int (n: mod n 16 == 0);
       default = 1024; # 1GB; default from upstream beesd script
-      description = ''
+      description = lib.mdDoc ''
         Hash table size in MB; must be a multiple of 16.
-        </para>
-        <para>
+
         A larger ratio of index size to storage size means smaller blocks of
         duplicate content are recognized.
-        </para>
-        <para>
+
         If you have 1TB of data, a 4GB hash table (which is to say, a value of
         4096) will permit 4KB extents (the smallest possible size) to be
         recognized, whereas a value of 1024 -- creating a 1GB hash table --
@@ -47,12 +44,12 @@ let
       type = types.enum (attrNames logLevels ++ attrValues logLevels);
       apply = v: if isString v then logLevels.${v} else v;
       default = "info";
-      description = "Log verbosity (syslog keyword/level).";
+      description = lib.mdDoc "Log verbosity (syslog keyword/level).";
     };
     options.workDir = mkOption {
       type = str;
       default = ".beeshome";
-      description = ''
+      description = lib.mdDoc ''
         Name (relative to the root of the filesystem) of the subvolume where
         the hash table will be stored.
       '';
@@ -60,7 +57,7 @@ let
     options.extraOptions = mkOption {
       type = listOf str;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         Extra command-line options passed to the daemon. See upstream bees documentation.
       '';
       example = literalExpression ''
@@ -75,7 +72,7 @@ in
   options.services.beesd = {
     filesystems = mkOption {
       type = with types; attrsOf (submodule fsOptions);
-      description = "BTRFS filesystems to run block-level deduplication on.";
+      description = lib.mdDoc "BTRFS filesystems to run block-level deduplication on.";
       default = { };
       example = literalExpression ''
         {
diff --git a/nixos/modules/services/misc/bepasty.nix b/nixos/modules/services/misc/bepasty.nix
index f69832e5b2bd..70d07629493b 100644
--- a/nixos/modules/services/misc/bepasty.nix
+++ b/nixos/modules/services/misc/bepasty.nix
@@ -13,11 +13,11 @@ let
 in
 {
   options.services.bepasty = {
-    enable = mkEnableOption "Bepasty servers";
+    enable = mkEnableOption (lib.mdDoc "Bepasty servers");
 
     servers = mkOption {
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         configure a number of bepasty servers which will be started with
         gunicorn.
         '';
@@ -27,7 +27,7 @@ in
 
           bind = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               Bind address to be used for this server.
               '';
             example = "0.0.0.0:8000";
@@ -36,7 +36,7 @@ in
 
           dataDir = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               Path to the directory where the pastes will be saved to
               '';
             default = default_home+"/data";
@@ -44,7 +44,7 @@ in
 
           defaultPermissions = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               default permissions for all unauthenticated accesses.
               '';
             example = "read,create,delete";
@@ -53,7 +53,7 @@ in
 
           extraConfig = mkOption {
             type = types.lines;
-            description = ''
+            description = lib.mdDoc ''
               Extra configuration for bepasty server to be appended on the
               configuration.
               see https://bepasty-server.readthedocs.org/en/latest/quickstart.html#configuring-bepasty
@@ -70,13 +70,13 @@ in
 
           secretKey = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               server secret for safe session cookies, must be set.
 
               Warning: this secret is stored in the WORLD-READABLE Nix store!
 
-              It's recommended to use <option>secretKeyFile</option>
-              which takes precedence over <option>secretKey</option>.
+              It's recommended to use {option}`secretKeyFile`
+              which takes precedence over {option}`secretKey`.
               '';
             default = "";
           };
@@ -84,19 +84,19 @@ in
           secretKeyFile = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               A file that contains the server secret for safe session cookies, must be set.
 
-              <option>secretKeyFile</option> takes precedence over <option>secretKey</option>.
+              {option}`secretKeyFile` takes precedence over {option}`secretKey`.
 
-              Warning: when <option>secretKey</option> is non-empty <option>secretKeyFile</option>
+              Warning: when {option}`secretKey` is non-empty {option}`secretKeyFile`
               defaults to a file in the WORLD-READABLE Nix store containing that secret.
               '';
           };
 
           workDir = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               Path to the working directory (used for config and pidfile).
               Defaults to the users home directory.
               '';
diff --git a/nixos/modules/services/misc/calibre-server.nix b/nixos/modules/services/misc/calibre-server.nix
index 2467d34b524a..77c60381a312 100644
--- a/nixos/modules/services/misc/calibre-server.nix
+++ b/nixos/modules/services/misc/calibre-server.nix
@@ -23,23 +23,23 @@ in
   options = {
     services.calibre-server = {
 
-      enable = mkEnableOption "calibre-server";
+      enable = mkEnableOption (lib.mdDoc "calibre-server");
 
       libraries = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The directories of the libraries to serve. They must be readable for the user under which the server runs.
         '';
         type = types.listOf types.path;
       };
 
       user = mkOption {
-        description = "The user under which calibre-server runs.";
+        description = lib.mdDoc "The user under which calibre-server runs.";
         type = types.str;
         default = "calibre-server";
       };
 
       group = mkOption {
-        description = "The group under which calibre-server runs.";
+        description = lib.mdDoc "The group under which calibre-server runs.";
         type = types.str;
         default = "calibre-server";
       };
diff --git a/nixos/modules/services/misc/canto-daemon.nix b/nixos/modules/services/misc/canto-daemon.nix
index db51a263aab5..8150e038bc13 100644
--- a/nixos/modules/services/misc/canto-daemon.nix
+++ b/nixos/modules/services/misc/canto-daemon.nix
@@ -16,7 +16,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the canto RSS daemon.";
+        description = lib.mdDoc "Whether to enable the canto RSS daemon.";
       };
     };
 
diff --git a/nixos/modules/services/misc/cfdyndns.nix b/nixos/modules/services/misc/cfdyndns.nix
index 5885617d7429..9cd8b188ffae 100644
--- a/nixos/modules/services/misc/cfdyndns.nix
+++ b/nixos/modules/services/misc/cfdyndns.nix
@@ -14,11 +14,11 @@ in
 
   options = {
     services.cfdyndns = {
-      enable = mkEnableOption "Cloudflare Dynamic DNS Client";
+      enable = mkEnableOption (lib.mdDoc "Cloudflare Dynamic DNS Client");
 
       email = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The email address to use to authenticate to CloudFlare.
         '';
       };
@@ -26,7 +26,7 @@ in
       apikeyFile = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           The path to a file containing the API Key
           used to authenticate with CloudFlare.
         '';
@@ -36,7 +36,7 @@ in
         default = [];
         example = [ "host.tld" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The records to update in CloudFlare.
         '';
       };
diff --git a/nixos/modules/services/misc/cgminer.nix b/nixos/modules/services/misc/cgminer.nix
index 60f75530723b..fced106cb325 100644
--- a/nixos/modules/services/misc/cgminer.nix
+++ b/nixos/modules/services/misc/cgminer.nix
@@ -31,25 +31,25 @@ 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;
         defaultText = literalExpression "pkgs.cgminer";
-        description = "Which cgminer derivation to use.";
+        description = lib.mdDoc "Which cgminer derivation to use.";
         type = types.package;
       };
 
       user = mkOption {
         type = types.str;
         default = "cgminer";
-        description = "User account under which cgminer runs";
+        description = lib.mdDoc "User account under which cgminer runs";
       };
 
       pools = mkOption {
         default = [];  # Run benchmark
         type = types.listOf (types.attrsOf types.str);
-        description = "List of pools where to mine";
+        description = lib.mdDoc "List of pools where to mine";
         example = [{
           url = "http://p2pool.org:9332";
           username = "17EUZxTvs9uRmPsjPZSYUU3zCz9iwstudk";
@@ -60,7 +60,7 @@ in
       hardware = mkOption {
         default = []; # Run without options
         type = types.listOf (types.attrsOf (types.either types.str types.int));
-        description= "List of config options for every GPU";
+        description= lib.mdDoc "List of config options for every GPU";
         example = [
         {
           intensity = 9;
@@ -87,7 +87,7 @@ in
       config = mkOption {
         default = {};
         type = types.attrsOf (types.either types.bool types.int);
-        description = "Additional config";
+        description = lib.mdDoc "Additional config";
         example = {
           auto-fan = true;
           auto-gpu = true;
diff --git a/nixos/modules/services/misc/clipcat.nix b/nixos/modules/services/misc/clipcat.nix
index 8b749aa72896..0129de3a9efb 100644
--- a/nixos/modules/services/misc/clipcat.nix
+++ b/nixos/modules/services/misc/clipcat.nix
@@ -7,13 +7,13 @@ let
 in {
 
   options.services.clipcat= {
-    enable = mkEnableOption "Clipcat clipboard daemon";
+    enable = mkEnableOption (lib.mdDoc "Clipcat clipboard daemon");
 
     package = mkOption {
       type = types.package;
       default = pkgs.clipcat;
       defaultText = literalExpression "pkgs.clipcat";
-      description = "clipcat derivation to use.";
+      description = lib.mdDoc "clipcat derivation to use.";
     };
   };
 
diff --git a/nixos/modules/services/misc/clipmenu.nix b/nixos/modules/services/misc/clipmenu.nix
index ef95985f8d8a..1cc8c4c47f7e 100644
--- a/nixos/modules/services/misc/clipmenu.nix
+++ b/nixos/modules/services/misc/clipmenu.nix
@@ -7,13 +7,13 @@ 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;
       default = pkgs.clipmenu;
       defaultText = literalExpression "pkgs.clipmenu";
-      description = "clipmenu derivation to use.";
+      description = lib.mdDoc "clipmenu derivation to use.";
     };
   };
 
diff --git a/nixos/modules/services/misc/confd.nix b/nixos/modules/services/misc/confd.nix
index 6c66786524ba..17c1be57ccbc 100755
--- a/nixos/modules/services/misc/confd.nix
+++ b/nixos/modules/services/misc/confd.nix
@@ -17,52 +17,52 @@ let
 
 in {
   options.services.confd = {
-    enable = mkEnableOption "confd service";
+    enable = mkEnableOption (lib.mdDoc "confd service");
 
     backend = mkOption {
-      description = "Confd config storage backend to use.";
+      description = lib.mdDoc "Confd config storage backend to use.";
       default = "etcd";
       type = types.enum ["etcd" "consul" "redis" "zookeeper"];
     };
 
     interval = mkOption {
-      description = "Confd check interval.";
+      description = lib.mdDoc "Confd check interval.";
       default = 10;
       type = types.int;
     };
 
     nodes = mkOption {
-      description = "Confd list of nodes to connect to.";
+      description = lib.mdDoc "Confd list of nodes to connect to.";
       default = [ "http://127.0.0.1:2379" ];
       type = types.listOf types.str;
     };
 
     watch = mkOption {
-      description = "Confd, whether to watch etcd config for changes.";
+      description = lib.mdDoc "Confd, whether to watch etcd config for changes.";
       default = true;
       type = types.bool;
     };
 
     prefix = mkOption {
-      description = "The string to prefix to keys.";
+      description = lib.mdDoc "The string to prefix to keys.";
       default = "/";
       type = types.path;
     };
 
     logLevel = mkOption {
-      description = "Confd log level.";
+      description = lib.mdDoc "Confd log level.";
       default = "info";
       type = types.enum ["info" "debug"];
     };
 
     confDir = mkOption {
-      description = "The path to the confd configs.";
+      description = lib.mdDoc "The path to the confd configs.";
       default = "/etc/confd";
       type = types.path;
     };
 
     package = mkOption {
-      description = "Confd package to use.";
+      description = lib.mdDoc "Confd package to use.";
       default = pkgs.confd;
       defaultText = literalExpression "pkgs.confd";
       type = types.package;
diff --git a/nixos/modules/services/misc/cpuminer-cryptonight.nix b/nixos/modules/services/misc/cpuminer-cryptonight.nix
index 907b9d90da29..7b18c6b3cd20 100644
--- a/nixos/modules/services/misc/cpuminer-cryptonight.nix
+++ b/nixos/modules/services/misc/cpuminer-cryptonight.nix
@@ -23,27 +23,27 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the cpuminer cryptonight miner.
         '';
       };
       url = mkOption {
         type = types.str;
-        description = "URL of mining server";
+        description = lib.mdDoc "URL of mining server";
       };
       user = mkOption {
         type = types.str;
-        description = "Username for mining server";
+        description = lib.mdDoc "Username for mining server";
       };
       pass = mkOption {
         type = types.str;
         default = "x";
-        description = "Password for mining server";
+        description = lib.mdDoc "Password for mining server";
       };
       threads = mkOption {
         type = types.int;
         default = 0;
-        description = "Number of miner threads, defaults to available processors";
+        description = lib.mdDoc "Number of miner threads, defaults to available processors";
       };
     };
 
diff --git a/nixos/modules/services/misc/devmon.nix b/nixos/modules/services/misc/devmon.nix
index e4a3348646b1..bd0b738b7018 100644
--- a/nixos/modules/services/misc/devmon.nix
+++ b/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/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix
index 96e2a4e7c260..4b714b84f3b2 100644
--- a/nixos/modules/services/misc/dictd.nix
+++ b/nixos/modules/services/misc/dictd.nix
@@ -17,7 +17,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the DICT.org dictionary server.
         '';
       };
@@ -27,7 +27,7 @@ in
         default = with pkgs.dictdDBs; [ wiktionary wordnet ];
         defaultText = literalExpression "with pkgs.dictdDBs; [ wiktionary wordnet ]";
         example = literalExpression "[ pkgs.dictdDBs.nld2eng ]";
-        description = "List of databases to make available.";
+        description = lib.mdDoc "List of databases to make available.";
       };
 
     };
@@ -45,6 +45,10 @@ in
     # get the command line client on system path to make some use of the service
     environment.systemPackages = [ pkgs.dict ];
 
+    environment.etc."dict.conf".text = ''
+      server localhost
+    '';
+
     users.users.dictd =
       { group = "dictd";
         description = "DICT.org dictd server";
diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix
index 07c0613336aa..1cdfeef57cef 100644
--- a/nixos/modules/services/misc/disnix.nix
+++ b/nixos/modules/services/misc/disnix.nix
@@ -17,29 +17,29 @@ in
 
     services.disnix = {
 
-      enable = mkEnableOption "Disnix";
+      enable = mkEnableOption (lib.mdDoc "Disnix");
 
       enableMultiUser = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to support multi-user mode by enabling the Disnix D-Bus service";
+        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;
-        description = "The Disnix package";
+        description = lib.mdDoc "The Disnix package";
         default = pkgs.disnix;
         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;
         default = [ "default" ];
-        description = "Names of the Disnix profiles to expose in the system's PATH";
+        description = lib.mdDoc "Names of the Disnix profiles to expose in the system's PATH";
       };
     };
 
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index cb68a29c530b..98edb413f3c4 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -47,16 +47,16 @@ let
 
 in {
   options.services.dockerRegistry = {
-    enable = mkEnableOption "Docker Registry";
+    enable = mkEnableOption (lib.mdDoc "Docker Registry");
 
     listenAddress = mkOption {
-      description = "Docker registry host or ip to bind to.";
+      description = lib.mdDoc "Docker registry host or ip to bind to.";
       default = "127.0.0.1";
       type = types.str;
     };
 
     port = mkOption {
-      description = "Docker registry port to bind to.";
+      description = lib.mdDoc "Docker registry port to bind to.";
       default = 5000;
       type = types.port;
     };
@@ -64,7 +64,7 @@ in {
     storagePath = mkOption {
       type = types.nullOr types.path;
       default = "/var/lib/docker-registry";
-      description = ''
+      description = lib.mdDoc ''
         Docker registry storage path for the filesystem storage backend. Set to
         null to configure another backend via extraConfig.
       '';
@@ -73,40 +73,39 @@ in {
     enableDelete = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable delete for manifests and blobs.";
+      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;
       default = "localhost:6379";
-      description = "Set redis host and port.";
+      description = lib.mdDoc "Set redis host and port.";
     };
 
     redisPassword = mkOption {
       type = types.str;
       default = "";
-      description = "Set redis password.";
+      description = lib.mdDoc "Set redis password.";
     };
 
     extraConfig = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Docker extra registry configuration via environment variables.
       '';
       default = {};
       type = types.attrs;
     };
 
-    enableGarbageCollect = mkEnableOption "garbage collect";
+    enableGarbageCollect = mkEnableOption (lib.mdDoc "garbage collect");
 
     garbageCollectDates = mkOption {
       default = "daily";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Specification (in the format described by
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>) of the time at
+        {manpage}`systemd.time(7)`) of the time at
         which the garbage collect will occur.
       '';
     };
diff --git a/nixos/modules/services/misc/domoticz.nix b/nixos/modules/services/misc/domoticz.nix
index b1353d484048..fd9fcf0b78eb 100644
--- a/nixos/modules/services/misc/domoticz.nix
+++ b/nixos/modules/services/misc/domoticz.nix
@@ -12,18 +12,18 @@ in {
   options = {
 
     services.domoticz = {
-      enable = mkEnableOption pkgDesc;
+      enable = mkEnableOption (lib.mdDoc pkgDesc);
 
       bind = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "IP address to bind to.";
+        description = lib.mdDoc "IP address to bind to.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8080;
-        description = "Port to bind to for HTTP, set to 0 to disable HTTP.";
+        description = lib.mdDoc "Port to bind to for HTTP, set to 0 to disable HTTP.";
       };
 
     };
diff --git a/nixos/modules/services/misc/duckling.nix b/nixos/modules/services/misc/duckling.nix
index 77d2a92380b0..4d06ca7fa667 100644
--- a/nixos/modules/services/misc/duckling.nix
+++ b/nixos/modules/services/misc/duckling.nix
@@ -7,12 +7,12 @@ let
 in {
   options = {
     services.duckling = {
-      enable = mkEnableOption "duckling";
+      enable = mkEnableOption (lib.mdDoc "duckling");
 
       port = mkOption {
         type = types.port;
         default = 8080;
-        description = ''
+        description = lib.mdDoc ''
           Port on which duckling will run.
         '';
       };
diff --git a/nixos/modules/services/misc/dwm-status.nix b/nixos/modules/services/misc/dwm-status.nix
index 5f591b3c5d41..de3e28c41d27 100644
--- a/nixos/modules/services/misc/dwm-status.nix
+++ b/nixos/modules/services/misc/dwm-status.nix
@@ -22,21 +22,21 @@ in
 
     services.dwm-status = {
 
-      enable = mkEnableOption "dwm-status user service";
+      enable = mkEnableOption (lib.mdDoc "dwm-status user service");
 
       package = mkOption {
         type = types.package;
         default = pkgs.dwm-status;
         defaultText = literalExpression "pkgs.dwm-status";
         example = literalExpression "pkgs.dwm-status.override { enableAlsaUtils = false; }";
-        description = ''
+        description = lib.mdDoc ''
           Which dwm-status package to use.
         '';
       };
 
       order = mkOption {
         type = types.listOf (types.enum [ "audio" "backlight" "battery" "cpu_load" "network" "time" ]);
-        description = ''
+        description = lib.mdDoc ''
           List of enabled features in order.
         '';
       };
@@ -44,7 +44,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra config in TOML format.
         '';
       };
diff --git a/nixos/modules/services/misc/dysnomia.nix b/nixos/modules/services/misc/dysnomia.nix
index 7d9c39a69737..0f92265ccbea 100644
--- a/nixos/modules/services/misc/dysnomia.nix
+++ b/nixos/modules/services/misc/dysnomia.nix
@@ -87,52 +87,52 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable Dysnomia";
+        description = lib.mdDoc "Whether to enable Dysnomia";
       };
 
       enableAuthentication = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to publish privacy-sensitive authentication credentials";
+        description = lib.mdDoc "Whether to publish privacy-sensitive authentication credentials";
       };
 
       package = mkOption {
         type = types.path;
-        description = "The Dysnomia package";
+        description = lib.mdDoc "The Dysnomia package";
       };
 
       properties = mkOption {
-        description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions.";
+        description = lib.mdDoc "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions.";
         default = {};
         type = types.attrs;
       };
 
       containers = mkOption {
-        description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties";
+        description = lib.mdDoc "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties";
         default = {};
         type = types.attrsOf types.attrs;
       };
 
       components = mkOption {
-        description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state";
+        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;
       };
 
       extraContainerProperties = mkOption {
-        description = "An attribute set providing additional container settings in addition to the default properties";
+        description = lib.mdDoc "An attribute set providing additional container settings in addition to the default properties";
         default = {};
         type = types.attrs;
       };
 
       extraContainerPaths = mkOption {
-        description = "A list of paths containing additional container configurations that are added to the search folders";
+        description = lib.mdDoc "A list of paths containing additional container configurations that are added to the search folders";
         default = [];
         type = types.listOf types.path;
       };
 
       extraModulePaths = mkOption {
-        description = "A list of paths containing additional modules that are added to the search folders";
+        description = lib.mdDoc "A list of paths containing additional modules that are added to the search folders";
         default = [];
         type = types.listOf types.path;
       };
@@ -140,7 +140,7 @@ in
       enableLegacyModules = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable Dysnomia legacy process and wrapper modules";
+        description = lib.mdDoc "Whether to enable Dysnomia legacy process and wrapper modules";
       };
     };
   };
@@ -186,7 +186,7 @@ in
 
     dysnomia.properties = {
       hostname = config.networking.hostName;
-      inherit (config.nixpkgs.localSystem) system;
+      inherit (pkgs.stdenv.hostPlatform) system;
 
       supportedTypes = [
         "echo"
diff --git a/nixos/modules/services/misc/errbot.nix b/nixos/modules/services/misc/errbot.nix
index b447ba5d438d..a650bc5bbd92 100644
--- a/nixos/modules/services/misc/errbot.nix
+++ b/nixos/modules/services/misc/errbot.nix
@@ -27,48 +27,48 @@ in {
   options = {
     services.errbot.instances = mkOption {
       default = {};
-      description = "Errbot instance configs";
+      description = lib.mdDoc "Errbot instance configs";
       type = types.attrsOf (types.submodule {
         options = {
           dataDir = mkOption {
             type = types.nullOr types.path;
             default = null;
-            description = "Data directory for errbot instance.";
+            description = lib.mdDoc "Data directory for errbot instance.";
           };
 
           plugins = mkOption {
             type = types.listOf types.package;
             default = [];
-            description = "List of errbot plugin derivations.";
+            description = lib.mdDoc "List of errbot plugin derivations.";
           };
 
           logLevel = mkOption {
             type = types.str;
             default = "INFO";
-            description = "Errbot log level";
+            description = lib.mdDoc "Errbot log level";
           };
 
           admins = mkOption {
             type = types.listOf types.str;
             default = [];
-            description = "List of identifiers of errbot admins.";
+            description = lib.mdDoc "List of identifiers of errbot admins.";
           };
 
           backend = mkOption {
             type = types.str;
             default = "XMPP";
-            description = "Errbot backend name.";
+            description = lib.mdDoc "Errbot backend name.";
           };
 
           identity = mkOption {
             type = types.attrs;
-            description = "Errbot identity configuration";
+            description = lib.mdDoc "Errbot identity configuration";
           };
 
           extraConfig = mkOption {
             type = types.lines;
             default = "";
-            description = "String to be appended to the config verbatim";
+            description = lib.mdDoc "String to be appended to the config verbatim";
           };
         };
       });
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
index 3925b7dd1636..3343e94778a2 100644
--- a/nixos/modules/services/misc/etcd.nix
+++ b/nixos/modules/services/misc/etcd.nix
@@ -10,124 +10,124 @@ in {
 
   options.services.etcd = {
     enable = mkOption {
-      description = "Whether to enable etcd.";
+      description = lib.mdDoc "Whether to enable etcd.";
       default = false;
       type = types.bool;
     };
 
     name = mkOption {
-      description = "Etcd unique node name.";
+      description = lib.mdDoc "Etcd unique node name.";
       default = config.networking.hostName;
       defaultText = literalExpression "config.networking.hostName";
       type = types.str;
     };
 
     advertiseClientUrls = mkOption {
-      description = "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
+      description = lib.mdDoc "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
       default = cfg.listenClientUrls;
       defaultText = literalExpression "config.${opt.listenClientUrls}";
       type = types.listOf types.str;
     };
 
     listenClientUrls = mkOption {
-      description = "Etcd list of URLs to listen on for client traffic.";
+      description = lib.mdDoc "Etcd list of URLs to listen on for client traffic.";
       default = ["http://127.0.0.1:2379"];
       type = types.listOf types.str;
     };
 
     listenPeerUrls = mkOption {
-      description = "Etcd list of URLs to listen on for peer traffic.";
+      description = lib.mdDoc "Etcd list of URLs to listen on for peer traffic.";
       default = ["http://127.0.0.1:2380"];
       type = types.listOf types.str;
     };
 
     initialAdvertisePeerUrls = mkOption {
-      description = "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
+      description = lib.mdDoc "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
       default = cfg.listenPeerUrls;
       defaultText = literalExpression "config.${opt.listenPeerUrls}";
       type = types.listOf types.str;
     };
 
     initialCluster = mkOption {
-      description = "Etcd initial cluster configuration for bootstrapping.";
+      description = lib.mdDoc "Etcd initial cluster configuration for bootstrapping.";
       default = ["${cfg.name}=http://127.0.0.1:2380"];
       defaultText = literalExpression ''["''${config.${opt.name}}=http://127.0.0.1:2380"]'';
       type = types.listOf types.str;
     };
 
     initialClusterState = mkOption {
-      description = "Etcd initial cluster configuration for bootstrapping.";
+      description = lib.mdDoc "Etcd initial cluster configuration for bootstrapping.";
       default = "new";
       type = types.enum ["new" "existing"];
     };
 
     initialClusterToken = mkOption {
-      description = "Etcd initial cluster token for etcd cluster during bootstrap.";
+      description = lib.mdDoc "Etcd initial cluster token for etcd cluster during bootstrap.";
       default = "etcd-cluster";
       type = types.str;
     };
 
     discovery = mkOption {
-      description = "Etcd discovery url";
+      description = lib.mdDoc "Etcd discovery url";
       default = "";
       type = types.str;
     };
 
     clientCertAuth = mkOption {
-      description = "Whether to use certs for client authentication";
+      description = lib.mdDoc "Whether to use certs for client authentication";
       default = false;
       type = types.bool;
     };
 
     trustedCaFile = mkOption {
-      description = "Certificate authority file to use for clients";
+      description = lib.mdDoc "Certificate authority file to use for clients";
       default = null;
       type = types.nullOr types.path;
     };
 
     certFile = mkOption {
-      description = "Cert file to use for clients";
+      description = lib.mdDoc "Cert file to use for clients";
       default = null;
       type = types.nullOr types.path;
     };
 
     keyFile = mkOption {
-      description = "Key file to use for clients";
+      description = lib.mdDoc "Key file to use for clients";
       default = null;
       type = types.nullOr types.path;
     };
 
     peerCertFile = mkOption {
-      description = "Cert file to use for peer to peer communication";
+      description = lib.mdDoc "Cert file to use for peer to peer communication";
       default = cfg.certFile;
       defaultText = literalExpression "config.${opt.certFile}";
       type = types.nullOr types.path;
     };
 
     peerKeyFile = mkOption {
-      description = "Key file to use for peer to peer communication";
+      description = lib.mdDoc "Key file to use for peer to peer communication";
       default = cfg.keyFile;
       defaultText = literalExpression "config.${opt.keyFile}";
       type = types.nullOr types.path;
     };
 
     peerTrustedCaFile = mkOption {
-      description = "Certificate authority file to use for peer to peer communication";
+      description = lib.mdDoc "Certificate authority file to use for peer to peer communication";
       default = cfg.trustedCaFile;
       defaultText = literalExpression "config.${opt.trustedCaFile}";
       type = types.nullOr types.path;
     };
 
     peerClientCertAuth = mkOption {
-      description = "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA";
+      description = lib.mdDoc "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA";
       default = false;
       type = types.bool;
     };
 
     extraConf = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Etcd extra configuration. See
-        <link xlink:href='https://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md#configuration-flags' />
+        <https://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md#configuration-flags>
       '';
       type = types.attrsOf types.str;
       default = {};
@@ -145,7 +145,7 @@ in {
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/etcd";
-      description = "Etcd data directory.";
+      description = lib.mdDoc "Etcd data directory.";
     };
   };
 
diff --git a/nixos/modules/services/misc/etebase-server.nix b/nixos/modules/services/misc/etebase-server.nix
index cb99364aa1a6..c3723d188146 100644
--- a/nixos/modules/services/misc/etebase-server.nix
+++ b/nixos/modules/services/misc/etebase-server.nix
@@ -36,12 +36,12 @@ in
         type = types.bool;
         default = false;
         example = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Etebase server.
 
           Once enabled you need to create an admin user by invoking the
-          shell command <literal>etebase-server createsuperuser</literal> with
-          the user specified by the <literal>user</literal> option or a superuser.
+          shell command `etebase-server createsuperuser` with
+          the user specified by the `user` option or a superuser.
           Then you can login and create accounts on your-etebase-server.com/admin
         '';
       };
@@ -49,19 +49,19 @@ in
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/etebase-server";
-        description = "Directory to store the Etebase server data.";
+        description = lib.mdDoc "Directory to store the Etebase server data.";
       };
 
       port = mkOption {
         type = with types; nullOr port;
         default = 8001;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to open ports in the firewall for the server.
         '';
       };
@@ -69,7 +69,7 @@ in
       unixSocket = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "The path to the socket to bind to.";
+        description = lib.mdDoc "The path to the socket to bind to.";
         example = "/run/etebase-server/etebase-server.sock";
       };
 
@@ -82,14 +82,14 @@ in
               debug = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Whether to set django's DEBUG flag.
                 '';
               };
               secret_file = mkOption {
                 type = with types; nullOr str;
                 default = null;
-                description = ''
+                description = lib.mdDoc ''
                   The path to a file containing the secret
                   used as django's SECRET_KEY.
                 '';
@@ -98,13 +98,13 @@ in
                 type = types.str;
                 default = "${cfg.dataDir}/static";
                 defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/static"'';
-                description = "The directory for static files.";
+                description = lib.mdDoc "The directory for static files.";
               };
               media_root = mkOption {
                 type = types.str;
                 default = "${cfg.dataDir}/media";
                 defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/media"'';
-                description = "The media directory.";
+                description = lib.mdDoc "The media directory.";
               };
             };
             allowed_hosts = {
@@ -112,7 +112,7 @@ in
                 type = types.str;
                 default = "0.0.0.0";
                 example = "localhost";
-                description = ''
+                description = lib.mdDoc ''
                   The main host that is allowed access.
                 '';
               };
@@ -121,22 +121,22 @@ in
               engine = mkOption {
                 type = types.enum [ "django.db.backends.sqlite3" "django.db.backends.postgresql" ];
                 default = "django.db.backends.sqlite3";
-                description = "The database engine to use.";
+                description = lib.mdDoc "The database engine to use.";
               };
               name = mkOption {
                 type = types.str;
                 default = "${cfg.dataDir}/db.sqlite3";
                 defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/db.sqlite3"'';
-                description = "The database name.";
+                description = lib.mdDoc "The database name.";
               };
             };
           };
         };
         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 = {
@@ -153,7 +153,7 @@ in
       user = mkOption {
         type = types.str;
         default = defaultUser;
-        description = "User under which Etebase server runs.";
+        description = lib.mdDoc "User under which Etebase server runs.";
       };
     };
   };
@@ -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 \
diff --git a/nixos/modules/services/misc/etesync-dav.nix b/nixos/modules/services/misc/etesync-dav.nix
index 9d7cfda371b1..9d99d548d95b 100644
--- a/nixos/modules/services/misc/etesync-dav.nix
+++ b/nixos/modules/services/misc/etesync-dav.nix
@@ -7,37 +7,37 @@ let
 in
   {
     options.services.etesync-dav = {
-      enable = mkEnableOption "etesync-dav";
+      enable = mkEnableOption (lib.mdDoc "etesync-dav");
 
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "The server host address.";
+        description = lib.mdDoc "The server host address.";
       };
 
       port = mkOption {
         type = types.port;
         default = 37358;
-        description = "The server host port.";
+        description = lib.mdDoc "The server host port.";
       };
 
       apiUrl = mkOption {
         type = types.str;
         default = "https://api.etesync.com/";
-        description = "The url to the etesync API.";
+        description = lib.mdDoc "The url to the etesync API.";
       };
 
       openFirewall = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to open the firewall for the specified port.";
+        description = lib.mdDoc "Whether to open the firewall for the specified port.";
       };
 
       sslCertificate = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/var/etesync.crt";
-        description = ''
+        description = lib.mdDoc ''
           Path to server SSL certificate. It will be copied into
           etesync-dav's data directory.
         '';
@@ -47,7 +47,7 @@ in
         type = types.nullOr types.path;
         default = null;
         example = "/var/etesync.key";
-        description = ''
+        description = lib.mdDoc ''
           Path to server SSL certificate key.  It will be copied into
           etesync-dav's data directory.
         '';
diff --git a/nixos/modules/services/misc/ethminer.nix b/nixos/modules/services/misc/ethminer.nix
deleted file mode 100644
index 223634669828..000000000000
--- a/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 = "Enable ethminer ether mining.";
-      };
-
-      recheckInterval = mkOption {
-        type = types.ints.unsigned;
-        default = 2000;
-        description = "Interval in milliseconds between farm rechecks.";
-      };
-
-      toolkit = mkOption {
-        type = types.enum [ "cuda" "opencl" ];
-        default = "cuda";
-        description = "Cuda or opencl toolkit.";
-      };
-
-      apiPort = mkOption {
-        type = types.int;
-        default = -3333;
-        description = "Ethminer api port. minus sign puts api in read-only mode.";
-      };
-
-      wallet = mkOption {
-        type = types.str;
-        example = "0x0123456789abcdef0123456789abcdef01234567";
-        description = "Ethereum wallet address.";
-      };
-
-      pool = mkOption {
-        type = types.str;
-        example = "eth-us-east1.nanopool.org";
-        description = "Mining pool address.";
-      };
-
-      stratumPort = mkOption {
-        type = types.port;
-        default = 9999;
-        description = "Stratum protocol tcp port.";
-      };
-
-      rig = mkOption {
-        type = types.str;
-        default = "mining-rig-name";
-        description = "Mining rig name.";
-      };
-
-      registerMail = mkOption {
-        type = types.str;
-        example = "email%40example.org";
-        description = "Url encoded email address to register with pool.";
-      };
-
-      maxPower = mkOption {
-        type = types.ints.unsigned;
-        default = 113;
-        description = "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/nixos/modules/services/misc/exhibitor.nix b/nixos/modules/services/misc/exhibitor.nix
index 4c935efbd844..91a87b55af59 100644
--- a/nixos/modules/services/misc/exhibitor.nix
+++ b/nixos/modules/services/misc/exhibitor.nix
@@ -68,81 +68,76 @@ 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 = ''
+        description = lib.mdDoc ''
           The port for exhibitor to listen on and communicate with other exhibitors.
         '';
       };
       baseDir = mkOption {
         type = types.str;
         default = "/var/exhibitor";
-        description = ''
+        description = lib.mdDoc ''
           Baseline directory for exhibitor runtime config.
         '';
       };
       configType = mkOption {
         type = types.enum [ "file" "s3" "zookeeper" "none" ];
-        description = ''
+        description = lib.mdDoc ''
           Which configuration type you want to use. Additional config will be
           required depending on which type you are using.
         '';
       };
       hostname = mkOption {
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Hostname to use and advertise
         '';
         default = null;
       };
       nodeModification = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether the Explorer UI will allow nodes to be modified (use with caution).
         '';
         default = true;
       };
       configCheckMs = mkOption {
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Period (ms) to check for shared config updates.
         '';
         default = 30000;
       };
       headingText = mkOption {
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Extra text to display in UI header
         '';
         default = null;
       };
       jqueryStyle = mkOption {
         type = types.enum [ "red" "black" "custom" ];
-        description = ''
+        description = lib.mdDoc ''
           Styling used for the JQuery-based UI.
         '';
         default = "red";
       };
       logLines = mkOption {
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
         Max lines of logging to keep in memory for display.
         '';
         default = 1000;
       };
       servo = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           ZooKeeper will be queried once a minute for its state via the 'mntr' four
           letter word (this requires ZooKeeper 3.4.x+). Servo will be used to publish
           this data via JMX.
@@ -151,14 +146,14 @@ in
       };
       timeout = mkOption {
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Connection timeout (ms) for ZK connections.
         '';
         default = 30000;
       };
       autoManageInstances = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Automatically manage ZooKeeper instances in the ensemble
         '';
         default = false;
@@ -167,7 +162,7 @@ in
         type = types.str;
         default = "${cfg.baseDir}/zkData";
         defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkData"'';
-        description = ''
+        description = lib.mdDoc ''
           The Zookeeper data directory
         '';
       };
@@ -175,56 +170,56 @@ in
         type = types.path;
         default = "${cfg.baseDir}/zkLogs";
         defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkLogs"'';
-        description = ''
+        description = lib.mdDoc ''
           The Zookeeper logs directory
         '';
       };
       extraConf = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra Exhibitor configuration to put in the ZooKeeper config file.
         '';
       };
       zkExtraCfg = mkOption {
         type = types.str;
         default = "initLimit=5&syncLimit=2&tickTime=2000";
-        description = ''
+        description = lib.mdDoc ''
           Extra options to pass into Zookeeper
         '';
       };
       zkClientPort = mkOption {
         type = types.int;
         default = 2181;
-        description = ''
+        description = lib.mdDoc ''
           Zookeeper client port
         '';
       };
       zkConnectPort = mkOption {
         type = types.int;
         default = 2888;
-        description = ''
+        description = lib.mdDoc ''
           The port to use for followers to talk to each other.
         '';
       };
       zkElectionPort = mkOption {
         type = types.int;
         default = 3888;
-        description = ''
+        description = lib.mdDoc ''
           The port for Zookeepers to use for leader election.
         '';
       };
       zkCleanupPeriod = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           How often (in milliseconds) to run the Zookeeper log cleanup task.
         '';
       };
       zkServersSpec = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Zookeeper server spec for all servers in the ensemble.
         '';
         example = [ "S:1:zk1.example.com" "S:2:zk2.example.com" "S:3:zk3.example.com" "O:4:zk-observer.example.com" ];
@@ -234,14 +229,14 @@ in
       s3Backup = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable backups to S3
         '';
       };
       fileSystemBackup = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enables file system backup of ZooKeeper log files
         '';
       };
@@ -249,21 +244,21 @@ in
       # Options for using zookeeper configType
       zkConfigConnect = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The initial connection string for ZooKeeper shared config storage
         '';
         example = ["host1:2181" "host2:2181"];
       };
       zkConfigExhibitorPath = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           If the ZooKeeper shared config is also running Exhibitor, the URI path for the REST call
         '';
         default = "/";
       };
       zkConfigExhibitorPort = mkOption {
         type = types.nullOr types.int;
-        description = ''
+        description = lib.mdDoc ''
           If the ZooKeeper shared config is also running Exhibitor, the port that
           Exhibitor is listening on. IMPORTANT: if this value is not set it implies
           that Exhibitor is not being used on the ZooKeeper shared config.
@@ -271,7 +266,7 @@ in
       };
       zkConfigPollMs = mkOption {
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           The period in ms to check for changes in the config ensemble
         '';
         default = 10000;
@@ -280,21 +275,21 @@ in
         sleepMs = mkOption {
           type = types.int;
           default = 1000;
-          description = ''
+          description = lib.mdDoc ''
             Retry sleep time connecting to the ZooKeeper config
           '';
         };
         retryQuantity = mkOption {
           type = types.int;
           default = 3;
-          description = ''
+          description = lib.mdDoc ''
             Retries connecting to the ZooKeeper config
           '';
         };
       };
       zkConfigZPath = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The base ZPath that Exhibitor should use
         '';
         example = "/exhibitor/config";
@@ -304,19 +299,19 @@ in
       s3Config = {
         bucketName = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Bucket name to store config
           '';
         };
         objectKey = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             S3 key name to store the config
           '';
         };
         configPrefix = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             When using AWS S3 shared config files, the prefix to use for values such as locks
           '';
           default = "exhibitor-";
@@ -326,7 +321,7 @@ in
       # The next two are used for either s3backup or s3 configType
       s3Credentials = mkOption {
         type = types.nullOr types.path;
-        description = ''
+        description = lib.mdDoc ''
           Optional credentials to use for s3backup or s3config. Argument is the path
           to an AWS credential properties file with two properties:
           com.netflix.exhibitor.s3.access-key-id and com.netflix.exhibitor.s3.access-secret-key
@@ -335,7 +330,7 @@ in
       };
       s3Region = mkOption {
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Optional region for S3 calls
         '';
         default = null;
@@ -344,7 +339,7 @@ in
       # Config options for file config type
       fsConfigDir = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Directory to store Exhibitor properties (cannot be used with s3config).
           Exhibitor uses file system locks so you can specify a shared location
           so as to enable complete ensemble management.
@@ -352,14 +347,14 @@ in
       };
       fsConfigLockPrefix = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           A prefix for a locking mechanism used in conjunction with fsconfigdir
         '';
         default = "exhibitor-lock-";
       };
       fsConfigName = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The name of the file to store config in
         '';
         default = "exhibitor.properties";
diff --git a/nixos/modules/services/misc/felix.nix b/nixos/modules/services/misc/felix.nix
index 0283de128afe..306d4cf0d7cf 100644
--- a/nixos/modules/services/misc/felix.nix
+++ b/nixos/modules/services/misc/felix.nix
@@ -17,25 +17,25 @@ 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;
         default = [ pkgs.felix_remoteshell ];
         defaultText = literalExpression "[ pkgs.felix_remoteshell ]";
-        description = "List of bundles that should be activated on startup";
+        description = lib.mdDoc "List of bundles that should be activated on startup";
       };
 
       user = mkOption {
         type = types.str;
         default = "osgi";
-        description = "User account under which Apache Felix runs.";
+        description = lib.mdDoc "User account under which Apache Felix runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "osgi";
-        description = "Group account under which Apache Felix runs.";
+        description = lib.mdDoc "Group account under which Apache Felix runs.";
       };
 
     };
diff --git a/nixos/modules/services/misc/freeswitch.nix b/nixos/modules/services/misc/freeswitch.nix
index 472b0b73ff69..b8b81e586944 100644
--- a/nixos/modules/services/misc/freeswitch.nix
+++ b/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 {
@@ -34,9 +34,9 @@ in {
         default = "${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
         defaultText = literalExpression ''"''${config.services.freeswitch.package}/share/freeswitch/conf/vanilla"'';
         example = literalExpression ''"''${config.services.freeswitch.package}/share/freeswitch/conf/minimal"'';
-        description = ''
+        description = lib.mdDoc ''
           Configuration template to use.
-          See available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
+          See available templates in [FreeSWITCH repository](https://github.com/signalwire/freeswitch/tree/master/conf).
           You can also set your own configuration directory.
         '';
       };
@@ -51,18 +51,18 @@ in {
             ''';
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Override file in FreeSWITCH config template directory.
           Each top-level attribute denotes a file path in the configuration directory, its value is the file path.
-          See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Default+Configuration">FreeSWITCH documentation</link> for more info.
-          Also check available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
+          See [FreeSWITCH documentation](https://freeswitch.org/confluence/display/FREESWITCH/Default+Configuration) for more info.
+          Also check available templates in [FreeSWITCH repository](https://github.com/signalwire/freeswitch/tree/master/conf).
         '';
       };
       package = mkOption {
         type = types.package;
         default = pkgs.freeswitch;
         defaultText = literalExpression "pkgs.freeswitch";
-        description = ''
+        description = lib.mdDoc ''
           FreeSWITCH package.
         '';
       };
diff --git a/nixos/modules/services/misc/fstrim.nix b/nixos/modules/services/misc/fstrim.nix
index a9fc04b46f0a..36b5f9c8cca1 100644
--- a/nixos/modules/services/misc/fstrim.nix
+++ b/nixos/modules/services/misc/fstrim.nix
@@ -11,18 +11,17 @@ 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;
         default = "weekly";
-        description = ''
+        description = lib.mdDoc ''
           How often we run fstrim. For most desktop and server systems
           a sufficient trimming frequency is once a week.
 
           The format is described in
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
     };
diff --git a/nixos/modules/services/misc/gammu-smsd.nix b/nixos/modules/services/misc/gammu-smsd.nix
index d4bb58d81dde..83f4efe695a2 100644
--- a/nixos/modules/services/misc/gammu-smsd.nix
+++ b/nixos/modules/services/misc/gammu-smsd.nix
@@ -45,52 +45,52 @@ 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;
         default = "smsd";
-        description = "User that has access to the device";
+        description = lib.mdDoc "User that has access to the device";
       };
 
       device = {
         path = mkOption {
           type = types.path;
-          description = "Device node or address of the phone";
+          description = lib.mdDoc "Device node or address of the phone";
           example = "/dev/ttyUSB2";
         };
 
         group = mkOption {
           type = types.str;
           default = "root";
-          description = "Owner group of the device";
+          description = lib.mdDoc "Owner group of the device";
           example = "dialout";
         };
 
         connection = mkOption {
           type = types.str;
           default = "at";
-          description = "Protocol which will be used to talk to the phone";
+          description = lib.mdDoc "Protocol which will be used to talk to the phone";
         };
 
         synchronizeTime = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether to set time from computer to the phone during starting connection";
+          description = lib.mdDoc "Whether to set time from computer to the phone during starting connection";
         };
 
         pin = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = "PIN code for the simcard";
+          description = lib.mdDoc "PIN code for the simcard";
         };
       };
 
@@ -99,13 +99,13 @@ in {
         file = mkOption {
           type = types.str;
           default = "syslog";
-          description = "Path to file where information about communication will be stored";
+          description = lib.mdDoc "Path to file where information about communication will be stored";
         };
 
         format = mkOption {
           type = types.enum [ "nothing" "text" "textall" "textalldate" "errors" "errorsdate" "binary" ];
           default = "errors";
-          description = "Determines what will be logged to the LogFile";
+          description = lib.mdDoc "Determines what will be logged to the LogFile";
         };
       };
 
@@ -114,14 +114,14 @@ in {
         gammu = mkOption {
           type = types.lines;
           default = "";
-          description = "Extra config lines to be added into [gammu] section";
+          description = lib.mdDoc "Extra config lines to be added into [gammu] section";
         };
 
 
         smsd = mkOption {
           type = types.lines;
           default = "";
-          description = "Extra config lines to be added into [smsd] section";
+          description = lib.mdDoc "Extra config lines to be added into [smsd] section";
         };
       };
 
@@ -130,69 +130,69 @@ in {
         service = mkOption {
           type = types.enum [ "null" "files" "sql" ];
           default = "null";
-          description = "Service to use to store sms data.";
+          description = lib.mdDoc "Service to use to store sms data.";
         };
 
         files = {
           inboxPath = mkOption {
             type = types.path;
             default = "/var/spool/sms/inbox/";
-            description = "Where the received SMSes are stored";
+            description = lib.mdDoc "Where the received SMSes are stored";
           };
 
           outboxPath = mkOption {
             type = types.path;
             default = "/var/spool/sms/outbox/";
-            description = "Where SMSes to be sent should be placed";
+            description = lib.mdDoc "Where SMSes to be sent should be placed";
           };
 
           sentSMSPath = mkOption {
             type = types.path;
             default = "/var/spool/sms/sent/";
-            description = "Where the transmitted SMSes are placed";
+            description = lib.mdDoc "Where the transmitted SMSes are placed";
           };
 
           errorSMSPath = mkOption {
             type = types.path;
             default = "/var/spool/sms/error/";
-            description = "Where SMSes with error in transmission is placed";
+            description = lib.mdDoc "Where SMSes with error in transmission is placed";
           };
         };
 
         sql = {
           driver = mkOption {
             type = types.enum [ "native_mysql" "native_pgsql" "odbc" "dbi" ];
-            description = "DB driver to use";
+            description = lib.mdDoc "DB driver to use";
           };
 
           sqlDialect = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = "SQL dialect to use (odbc driver only)";
+            description = lib.mdDoc "SQL dialect to use (odbc driver only)";
           };
 
           database = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = "Database name to store sms data";
+            description = lib.mdDoc "Database name to store sms data";
           };
 
           host = mkOption {
             type = types.str;
             default = "localhost";
-            description = "Database server address";
+            description = lib.mdDoc "Database server address";
           };
 
           user = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = "User name used for connection to the database";
+            description = lib.mdDoc "User name used for connection to the database";
           };
 
           password = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = "User password used for connetion to the database";
+            description = lib.mdDoc "User password used for connection to the database";
           };
         };
       };
diff --git a/nixos/modules/services/misc/geoipupdate.nix b/nixos/modules/services/misc/geoipupdate.nix
index 3211d4d88e4d..27c1157e9a8c 100644
--- a/nixos/modules/services/misc/geoipupdate.nix
+++ b/nixos/modules/services/misc/geoipupdate.nix
@@ -2,6 +2,7 @@
 
 let
   cfg = config.services.geoipupdate;
+  inherit (builtins) isAttrs isString isInt isList typeOf hashString;
 in
 {
   imports = [
@@ -10,28 +11,44 @@ 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;
         default = "weekly";
-        description = ''
+        description = lib.mdDoc ''
           Update the GeoIP databases at this time / interval.
           The format is described in
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
 
       settings = lib.mkOption {
-        description = ''
-          <productname>geoipupdate</productname> configuration
-          options. See
-          <link xlink:href="https://github.com/maxmind/geoipupdate/blob/main/doc/GeoIP.conf.md" />
+        example = lib.literalExpression ''
+          {
+            AccountID = 200001;
+            DatabaseDirectory = "/var/lib/GeoIP";
+            LicenseKey = { _secret = "/run/keys/maxmind_license_key"; };
+            Proxy = "10.0.0.10:8888";
+            ProxyUserPassword = { _secret = "/run/keys/proxy_pass"; };
+          }
+        '';
+        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
+          `_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
+          {file}`GeoIP.conf` file, the
+          `ProxyUserPassword` key will be set to the
+          contents of the
+          {file}`/run/keys/proxy_pass` file.
         '';
         type = lib.types.submodule {
           freeformType =
@@ -45,7 +62,7 @@ in
 
             AccountID = lib.mkOption {
               type = lib.types.int;
-              description = ''
+              description = lib.mdDoc ''
                 Your MaxMind account ID.
               '';
             };
@@ -57,29 +74,34 @@ in
                 "GeoLite2-City"
                 "GeoLite2-Country"
               ];
-              description = ''
+              description = lib.mdDoc ''
                 List of database edition IDs. This includes new string
-                IDs like <literal>GeoIP2-City</literal> and old
-                numeric IDs like <literal>106</literal>.
+                IDs like `GeoIP2-City` and old
+                numeric IDs like `106`.
               '';
             };
 
             LicenseKey = lib.mkOption {
-              type = lib.types.path;
-              description = ''
-                A file containing the <productname>MaxMind</productname>
-                license key.
+              type = with lib.types; either path (attrsOf path);
+              description = lib.mdDoc ''
+                A file containing the MaxMind license key.
+
+                Always handled as a secret whether the value is
+                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; };
             };
 
             DatabaseDirectory = lib.mkOption {
               type = lib.types.path;
               default = "/var/lib/GeoIP";
               example = "/run/GeoIP";
-              description = ''
+              description = lib.mdDoc ''
                 The directory to store the database files in. The
                 directory will be automatically created, the owner
-                changed to <literal>geoip</literal> and permissions
+                changed to `geoip` and permissions
                 set to world readable. This applies if the directory
                 already exists as well, so don't use a directory with
                 sensitive contents.
@@ -102,6 +124,9 @@ in
     systemd.services.geoipupdate-create-db-dir = {
       serviceConfig.Type = "oneshot";
       script = ''
+        set -o errexit -o pipefail -o nounset -o errtrace
+        shopt -s inherit_errexit
+
         mkdir -p ${cfg.settings.DatabaseDirectory}
         chmod 0755 ${cfg.settings.DatabaseDirectory}
       '';
@@ -115,32 +140,41 @@ in
         "network-online.target"
         "nss-lookup.target"
       ];
+      path = [ pkgs.replace-secret ];
       wants = [ "network-online.target" ];
       startAt = cfg.interval;
       serviceConfig = {
         ExecStartPre =
           let
+            isSecret = v: isAttrs v && v ? _secret && isString v._secret;
             geoipupdateKeyValue = lib.generators.toKeyValue {
               mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " " rec {
-                mkValueString = v: with builtins;
+                mkValueString = v:
                   if isInt           v then toString v
                   else if isString   v then v
                   else if true  ==   v then "1"
                   else if false ==   v then "0"
                   else if isList     v then lib.concatMapStringsSep " " mkValueString v
+                  else if isSecret   v then hashString "sha256" v._secret
                   else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
               };
             };
+            secretPaths = lib.catAttrs "_secret" (lib.collect isSecret cfg.settings);
+            mkSecretReplacement = file: ''
+              replace-secret ${lib.escapeShellArgs [ (hashString "sha256" file) file "/run/geoipupdate/GeoIP.conf" ]}
+            '';
+            secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
 
             geoipupdateConf = pkgs.writeText "geoipupdate.conf" (geoipupdateKeyValue cfg.settings);
 
             script = ''
+              set -o errexit -o pipefail -o nounset -o errtrace
+              shopt -s inherit_errexit
+
               chown geoip "${cfg.settings.DatabaseDirectory}"
 
               cp ${geoipupdateConf} /run/geoipupdate/GeoIP.conf
-              ${pkgs.replace-secret}/bin/replace-secret '${cfg.settings.LicenseKey}' \
-                                                        '${cfg.settings.LicenseKey}' \
-                                                        /run/geoipupdate/GeoIP.conf
+              ${secretReplacements}
             '';
           in
             "+${pkgs.writeShellScript "start-pre-full-privileges" script}";
@@ -149,7 +183,7 @@ in
         DynamicUser = true;
         ReadWritePaths = cfg.settings.DatabaseDirectory;
         RuntimeDirectory = "geoipupdate";
-        RuntimeDirectoryMode = 0700;
+        RuntimeDirectoryMode = "0700";
         CapabilityBoundingSet = "";
         PrivateDevices = true;
         PrivateMounts = true;
@@ -163,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/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index effa0c06ad6c..ceb4c1172854 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -10,6 +10,7 @@ let
   useMysql = cfg.database.type == "mysql";
   usePostgresql = cfg.database.type == "postgres";
   useSqlite = cfg.database.type == "sqlite3";
+  format = pkgs.formats.ini { };
   configFile = pkgs.writeText "app.ini" ''
     APP_NAME = ${cfg.appName}
     RUN_USER = ${cfg.user}
@@ -22,51 +23,47 @@ let
 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" "log" "level" ] [ "services" "gitea" "settings" "log" "LEVEL" ])
+    (mkRenamedOptionModule [ "services" "gitea" "log" "rootPath" ] [ "services" "gitea" "settings" "log" "ROOT_PATH" ])
+    (mkRenamedOptionModule [ "services" "gitea" "ssh" "clonePort" ] [ "services" "gitea" "settings" "server" "SSH_PORT" ])
+
+    (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")
+  ];
+
   options = {
     services.gitea = {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "Enable Gitea Service.";
+        description = lib.mdDoc "Enable Gitea Service.";
       };
 
       package = mkOption {
         default = pkgs.gitea;
         type = types.package;
         defaultText = literalExpression "pkgs.gitea";
-        description = "gitea derivation to use";
+        description = lib.mdDoc "gitea derivation to use";
       };
 
       useWizard = mkOption {
         default = false;
         type = types.bool;
-        description = "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator.";
+        description = lib.mdDoc "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator.";
       };
 
       stateDir = mkOption {
         default = "/var/lib/gitea";
         type = types.str;
-        description = "gitea data directory.";
-      };
-
-      log = {
-        rootPath = mkOption {
-          default = "${cfg.stateDir}/log";
-          defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
-          type = types.str;
-          description = "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 "gitea data directory.";
       };
 
       user = mkOption {
         type = types.str;
         default = "gitea";
-        description = "User account under which gitea runs.";
+        description = lib.mdDoc "User account under which gitea runs.";
       };
 
       database = {
@@ -74,45 +71,45 @@ in
           type = types.enum [ "sqlite3" "mysql" "postgres" ];
           example = "mysql";
           default = "sqlite3";
-          description = "Database engine to use.";
+          description = lib.mdDoc "Database engine to use.";
         };
 
         host = mkOption {
           type = types.str;
           default = "127.0.0.1";
-          description = "Database host address.";
+          description = lib.mdDoc "Database host address.";
         };
 
         port = mkOption {
           type = types.port;
-          default = (if !usePostgresql then 3306 else pg.port);
+          default = if !usePostgresql then 3306 else pg.port;
           defaultText = literalExpression ''
             if config.${opt.database.type} != "postgresql"
             then 3306
             else config.${options.services.postgresql.port}
           '';
-          description = "Database host port.";
+          description = lib.mdDoc "Database host port.";
         };
 
         name = mkOption {
           type = types.str;
           default = "gitea";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = mkOption {
           type = types.str;
           default = "gitea";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         password = mkOption {
           type = types.str;
           default = "";
-          description = ''
-            The password corresponding to <option>database.user</option>.
+          description = lib.mdDoc ''
+            The password corresponding to {option}`database.user`.
             Warning: this is stored in cleartext in the Nix store!
-            Use <option>database.passwordFile</option> instead.
+            Use {option}`database.passwordFile` instead.
           '';
         };
 
@@ -120,9 +117,9 @@ in
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/gitea-dbpassword";
-          description = ''
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
 
@@ -131,20 +128,20 @@ in
           default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
           defaultText = literalExpression "null";
           example = "/run/mysqld/mysqld.sock";
-          description = "Path to the unix socket file to use for authentication.";
+          description = lib.mdDoc "Path to the unix socket file to use for authentication.";
         };
 
         path = mkOption {
           type = types.str;
           default = "${cfg.stateDir}/data/gitea.db";
           defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/gitea.db"'';
-          description = "Path to the sqlite3 database file.";
+          description = lib.mdDoc "Path to the sqlite3 database file.";
         };
 
         createDatabase = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether to create a local database automatically.";
+          description = lib.mdDoc "Whether to create a local database automatically.";
         };
       };
 
@@ -152,7 +149,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable a timer that runs gitea dump to generate backup-files of the
             current gitea database and repositories.
           '';
@@ -162,12 +159,11 @@ in
           type = types.str;
           default = "04:31";
           example = "hourly";
-          description = ''
+          description = lib.mdDoc ''
             Run a gitea dump at this interval. Runs by default at 04:31 every day.
 
             The format is described in
-            <citerefentry><refentrytitle>systemd.time</refentrytitle>
-            <manvolnum>7</manvolnum></citerefentry>.
+            {manpage}`systemd.time(7)`.
           '';
         };
 
@@ -175,107 +171,79 @@ in
           type = types.str;
           default = "${cfg.stateDir}/dump";
           defaultText = literalExpression ''"''${config.${opt.stateDir}}/dump"'';
-          description = "Path to the dump files.";
+          description = lib.mdDoc "Path to the dump files.";
         };
 
         type = mkOption {
           type = types.enum [ "zip" "rar" "tar" "sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" ];
           default = "zip";
-          description = "Archive format used to store the dump file.";
+          description = lib.mdDoc "Archive format used to store the dump file.";
         };
 
         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";
         };
       };
 
-      ssh = {
-        enable = mkOption {
-          type = types.bool;
-          default = true;
-          description = "Enable external SSH feature.";
-        };
-
-        clonePort = mkOption {
-          type = types.int;
-          default = 22;
-          example = 2222;
-          description = ''
-            SSH port displayed in clone URL.
-            The option is required to configure a service when the external visible port
-            differs from the local listening port i.e. if port forwarding is used.
-          '';
-        };
-      };
-
       lfs = {
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = "Enables git-lfs support.";
+          description = lib.mdDoc "Enables git-lfs support.";
         };
 
         contentDir = mkOption {
           type = types.str;
           default = "${cfg.stateDir}/data/lfs";
           defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/lfs"'';
-          description = "Where to store LFS files.";
+          description = lib.mdDoc "Where to store LFS files.";
         };
       };
 
       appName = mkOption {
         type = types.str;
         default = "gitea: Gitea Service";
-        description = "Application name.";
+        description = lib.mdDoc "Application name.";
       };
 
       repositoryRoot = mkOption {
         type = types.str;
         default = "${cfg.stateDir}/repositories";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
-        description = "Path to the git repositories.";
+        description = lib.mdDoc "Path to the git repositories.";
       };
 
       domain = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Domain name of your server.";
+        description = lib.mdDoc "Domain name of your server.";
       };
 
       rootUrl = mkOption {
         type = types.str;
         default = "http://localhost:3000/";
-        description = "Full public URL of gitea server.";
+        description = lib.mdDoc "Full public URL of gitea server.";
       };
 
       httpAddress = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "HTTP listen address.";
+        description = lib.mdDoc "HTTP listen address.";
       };
 
       httpPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
-        description = "HTTP listen port.";
+        description = lib.mdDoc "HTTP listen port.";
       };
 
       enableUnixSocket = mkOption {
         type = types.bool;
         default = false;
-        description = "Configure Gitea to listen on a unix socket instead of the default TCP port.";
-      };
-
-      cookieSecure = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          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.
-        '';
+        description = lib.mdDoc "Configure Gitea to listen on a unix socket instead of the default TCP port.";
       };
 
       staticRootPath = mkOption {
@@ -283,33 +251,20 @@ in
         default = gitea.data;
         defaultText = literalExpression "package.data";
         example = "/var/lib/gitea/data";
-        description = "Upper level of template and static files path.";
+        description = lib.mdDoc "Upper level of template and static files path.";
       };
 
       mailerPasswordFile = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/var/lib/secrets/gitea/mailpw";
-        description = "Path to a file containing the SMTP password.";
-      };
-
-      disableRegistration = mkEnableOption "the registration lock" // {
-        description = ''
-          By default any user can create an account on this <literal>gitea</literal> 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
-          no install wizard is used.
-        '';
+        description = lib.mdDoc "Path to a file containing the SMTP password.";
       };
 
       settings = mkOption {
-        type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
         default = {};
-        description = ''
-          Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/>
+        description = lib.mdDoc ''
+          Gitea configuration. Refer to <https://docs.gitea.io/en-us/config-cheat-sheet/>
           for details on supported values.
         '';
         example = literalExpression ''
@@ -330,12 +285,74 @@ in
             };
           }
         '';
+        type = with types; submodule {
+          freeformType = format.type;
+          options = {
+            log = {
+              ROOT_PATH = mkOption {
+                default = "${cfg.stateDir}/log";
+                defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
+                type = types.str;
+                description = lib.mdDoc "Root path for log files.";
+              };
+              LEVEL = mkOption {
+                default = "Info";
+                type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
+                description = lib.mdDoc "General log level.";
+              };
+            };
+
+            server = {
+              DISABLE_SSH = mkOption {
+                type = types.bool;
+                default = false;
+                description = lib.mdDoc "Disable external SSH feature.";
+              };
+
+              SSH_PORT = mkOption {
+                type = types.port;
+                default = 22;
+                example = 2222;
+                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.
+                '';
+              };
+            };
+
+            service = {
+              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.
+
+                  *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.
+                '';
+              };
+            };
+
+            session = {
+              COOKIE_SECURE = mkOption {
+                type = types.bool;
+                default = false;
+                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.
+                '';
+              };
+            };
+          };
+        };
       };
 
       extraConfig = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "Configuration lines appended to the generated gitea configuration file.";
+        description = lib.mdDoc "Configuration lines appended to the generated gitea configuration file.";
       };
     };
   };
@@ -385,13 +402,6 @@ in
           HTTP_ADDR = cfg.httpAddress;
           HTTP_PORT = cfg.httpPort;
         })
-        (mkIf cfg.ssh.enable {
-          DISABLE_SSH = false;
-          SSH_PORT = cfg.ssh.clonePort;
-        })
-        (mkIf (!cfg.ssh.enable) {
-          DISABLE_SSH = true;
-        })
         (mkIf cfg.lfs.enable {
           LFS_START_SERVER = true;
           LFS_CONTENT_PATH = cfg.lfs.contentDir;
@@ -400,8 +410,7 @@ in
       ];
 
       session = {
-        COOKIE_NAME = "session";
-        COOKIE_SECURE = cfg.cookieSecure;
+        COOKIE_NAME = lib.mkDefault "session";
       };
 
       security = {
@@ -410,15 +419,6 @@ in
         INSTALL_LOCK = true;
       };
 
-      log = {
-        ROOT_PATH = cfg.log.rootPath;
-        LEVEL = cfg.log.level;
-      };
-
-      service = {
-        DISABLE_REGISTRATION = cfg.disableRegistration;
-      };
-
       mailer = mkIf (cfg.mailerPasswordFile != null) {
         PASSWD = "#mailerpass#";
       };
@@ -483,11 +483,11 @@ in
       description = "gitea";
       after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
       wantedBy = [ "multi-user.target" ];
-      path = [ gitea pkgs.git ];
+      path = [ gitea 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.
@@ -502,28 +502,28 @@ in
         replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
       in ''
         # copy custom configuration and generate a random secret key if needed
-        ${optionalString (cfg.useWizard == false) ''
+        ${optionalString (!cfg.useWizard) ''
           function gitea_setup {
             cp -f ${configFile} ${runConfig}
 
-            if [ ! -e ${secretKey} ]; then
+            if [ ! -s ${secretKey} ]; then
                 ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
             fi
 
             # Migrate LFS_JWT_SECRET filename
-            if [[ -e ${oldLfsJwtSecret} && ! -e ${lfsJwtSecret} ]]; then
+            if [[ -s ${oldLfsJwtSecret} && ! -s ${lfsJwtSecret} ]]; then
                 mv ${oldLfsJwtSecret} ${lfsJwtSecret}
             fi
 
-            if [ ! -e ${oauth2JwtSecret} ]; then
+            if [ ! -s ${oauth2JwtSecret} ]; then
                 ${gitea}/bin/gitea generate secret JWT_SECRET > ${oauth2JwtSecret}
             fi
 
-            if [ ! -e ${lfsJwtSecret} ]; then
+            if [ ! -s ${lfsJwtSecret} ]; then
                 ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${lfsJwtSecret}
             fi
 
-            if [ ! -e ${internalToken} ]; then
+            if [ ! -s ${internalToken} ]; then
                 ${gitea}/bin/gitea generate secret INTERNAL_TOKEN > ${internalToken}
             fi
 
@@ -592,7 +592,7 @@ 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 @memlock @module @mount @obsolete @raw-io @reboot @setuid @swap";
       };
 
       environment = {
@@ -622,10 +622,10 @@ in
 
     # Create database passwordFile default when password is configured.
     services.gitea.database.passwordFile =
-      (mkDefault (toString (pkgs.writeTextFile {
+      mkDefault (toString (pkgs.writeTextFile {
         name = "gitea-database-password";
         text = cfg.database.password;
-      })));
+      }));
 
     systemd.services.gitea-dump = mkIf cfg.dump.enable {
        description = "gitea dump";
diff --git a/nixos/modules/services/misc/gitit.nix b/nixos/modules/services/misc/gitit.nix
index 87dd97166b8e..0fafa76b5487 100644
--- a/nixos/modules/services/misc/gitit.nix
+++ b/nixos/modules/services/misc/gitit.nix
@@ -31,14 +31,14 @@ let
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable the gitit service.";
+        description = lib.mdDoc "Enable the gitit service.";
       };
 
       haskellPackages = mkOption {
         default = pkgs.haskellPackages;
         defaultText = literalExpression "pkgs.haskellPackages";
         example = literalExpression "pkgs.haskell.packages.ghc784";
-        description = "haskellPackages used to build gitit and plugins.";
+        description = lib.mdDoc "haskellPackages used to build gitit and plugins.";
       };
 
       extraPackages = mkOption {
@@ -49,41 +49,41 @@ let
             haskellPackages.wreq
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           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.
+          in {var}`haskellPackages` as the sole argument.
         '';
       };
 
       address = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "IP address on which the web server will listen.";
+        description = lib.mdDoc "IP address on which the web server will listen.";
       };
 
       port = mkOption {
         type = types.int;
         default = 5001;
-        description = "Port on which the web server will run.";
+        description = lib.mdDoc "Port on which the web server will run.";
       };
 
       wikiTitle = mkOption {
         type = types.str;
         default = "Gitit!";
-        description = "The wiki title.";
+        description = lib.mdDoc "The wiki title.";
       };
 
       repositoryType = mkOption {
         type = types.enum ["git" "darcs" "mercurial"];
         default = "git";
-        description = "Specifies the type of repository used for wiki content.";
+        description = lib.mdDoc "Specifies the type of repository used for wiki content.";
       };
 
       repositoryPath = mkOption {
         type = types.path;
         default = homeDir + "/wiki";
-        description = ''
+        description = lib.mdDoc ''
           Specifies the path of the repository directory. If it does not
           exist, gitit will create it on startup.
         '';
@@ -92,7 +92,7 @@ let
       requireAuthentication = mkOption {
         type = types.enum [ "none" "modify" "read" ];
         default = "modify";
-        description = ''
+        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
@@ -103,7 +103,7 @@ let
       authenticationMethod = mkOption {
         type = types.enum [ "form" "http" "generic" "github" ];
         default = "form";
-        description = ''
+        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
@@ -121,7 +121,7 @@ let
       userFile = mkOption {
         type = types.path;
         default = homeDir + "/gitit-users";
-        description = ''
+        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
@@ -132,7 +132,7 @@ let
       sessionTimeout = mkOption {
         type = types.int;
         default = 60;
-        description = ''
+        description = lib.mdDoc ''
           Number of minutes of inactivity before a session expires.
         '';
       };
@@ -140,7 +140,7 @@ let
       staticDir = mkOption {
         type = types.path;
         default = gititShared + "/data/static";
-        description = ''
+        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.
@@ -150,7 +150,7 @@ let
       defaultPageType = mkOption {
         type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ];
         default = "markdown";
-        description = ''
+        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
@@ -166,7 +166,7 @@ let
       math = mkOption {
         type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ];
         default = "mathml";
-        description = ''
+        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,
@@ -186,7 +186,7 @@ let
       mathJaxScript = mkOption {
         type = types.str;
         default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
-        description = ''
+        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:
@@ -202,7 +202,7 @@ let
       showLhsBirdTracks = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether to show Haskell code blocks in "bird style", with
           "> " at the beginning of each line.
         '';
@@ -211,7 +211,7 @@ let
       templatesDir = mkOption {
         type = types.path;
         default = gititShared + "/data/templates";
-        description = ''
+        description = lib.mdDoc ''
           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
@@ -224,7 +224,7 @@ let
       logFile = mkOption {
         type = types.path;
         default = homeDir + "/gitit.log";
-        description = ''
+        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.
         '';
@@ -233,7 +233,7 @@ let
       logLevel = mkOption {
         type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ];
         default = "ERROR";
-        description = ''
+        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.
@@ -243,7 +243,7 @@ let
       frontPage = mkOption {
         type = types.str;
         default = "Front Page";
-        description = ''
+        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.
@@ -253,7 +253,7 @@ let
       noDelete = mkOption {
         type = types.str;
         default = "Front Page, Help";
-        description = ''
+        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
@@ -264,7 +264,7 @@ let
       noEdit = mkOption {
         type = types.str;
         default = "Help";
-        description = ''
+        description = lib.mdDoc ''
           Specifies pages that cannot be edited through the web interface.
           Leave blank to allow every page to be edited.
         '';
@@ -273,7 +273,7 @@ let
       defaultSummary = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        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
@@ -284,7 +284,7 @@ let
       tableOfContents = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether to print a tables of contents (with links to
           sections) on each wiki page.
         '';
@@ -293,7 +293,7 @@ let
       plugins = mkOption {
         type = with types; listOf str;
         default = [ (gititShared + "/plugins/Dot.hs") ];
-        description = ''
+        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
@@ -304,7 +304,7 @@ let
       useCache = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        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.
         '';
@@ -313,13 +313,13 @@ let
       cacheDir = mkOption {
         type = types.path;
         default = homeDir + "/cache";
-        description = "Path where rendered pages will be cached.";
+        description = lib.mdDoc "Path where rendered pages will be cached.";
       };
 
       maxUploadSize = mkOption {
         type = types.str;
         default = "1000K";
-        description = ''
+        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
@@ -330,32 +330,32 @@ let
       maxPageSize = mkOption {
         type = types.str;
         default = "1000K";
-        description = "Specifies an upper limit on the size (in bytes) of pages.";
+        description = lib.mdDoc "Specifies an upper limit on the size (in bytes) of pages.";
       };
 
       debugMode = mkOption {
         type = types.bool;
         default = false;
-        description = "Causes debug information to be logged while gitit is running.";
+        description = lib.mdDoc "Causes debug information to be logged while gitit is running.";
       };
 
       compressResponses = mkOption {
         type = types.bool;
         default = true;
-        description = "Specifies whether HTTP responses should be compressed.";
+        description = lib.mdDoc "Specifies whether HTTP responses should be compressed.";
       };
 
       mimeTypesFile = mkOption {
         type = types.path;
         default = "/etc/mime/types.info";
-        description = ''
+        description = lib.mdDoc ''
           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>
+          ```
+          video/x-ms-wmx  wmx
+          ```
           If the file is not found, some simple defaults will be used.
         '';
       };
@@ -363,7 +363,7 @@ video/x-ms-wmx  wmx
       useReCaptcha = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If true, causes gitit to use the reCAPTCHA service
           (http://recaptcha.net) to prevent bots from creating accounts.
         '';
@@ -372,7 +372,7 @@ video/x-ms-wmx  wmx
       reCaptchaPrivateKey = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the private key for the reCAPTCHA service.  To get
           these, you need to create an account at http://recaptcha.net.
         '';
@@ -381,7 +381,7 @@ video/x-ms-wmx  wmx
       reCaptchaPublicKey = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the public key for the reCAPTCHA service.  To get
           these, you need to create an account at http://recaptcha.net.
         '';
@@ -390,7 +390,7 @@ video/x-ms-wmx  wmx
       accessQuestion = mkOption {
         type = types.str;
         default = "What is the code given to you by Ms. X?";
-        description = ''
+        description = lib.mdDoc ''
           Specifies a question that users must answer when they attempt to
           create an account
         '';
@@ -399,7 +399,7 @@ video/x-ms-wmx  wmx
       accessQuestionAnswers = mkOption {
         type = types.str;
         default = "RED DOG, red dog";
-        description = ''
+        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
@@ -413,7 +413,7 @@ video/x-ms-wmx  wmx
       rpxDomain = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        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.
@@ -423,13 +423,13 @@ video/x-ms-wmx  wmx
       rpxKey = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "RPX account access key.";
+        description = lib.mdDoc "RPX account access key.";
       };
 
       mailCommand = mkOption {
         type = types.str;
         default = "sendmail %s";
-        description = ''
+        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,
@@ -451,7 +451,7 @@ video/x-ms-wmx  wmx
           >
           > Regards
         '';
-        description = ''
+        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
@@ -471,7 +471,7 @@ video/x-ms-wmx  wmx
       useFeed = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether an ATOM feed should be enabled (for the site and
           for individual pages).
         '';
@@ -480,7 +480,7 @@ video/x-ms-wmx  wmx
       baseUrl = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        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'.
@@ -490,10 +490,10 @@ video/x-ms-wmx  wmx
       absoluteUrls = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           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
+          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'.
         '';
@@ -502,19 +502,19 @@ video/x-ms-wmx  wmx
       feedDays = mkOption {
         type = types.int;
         default = 14;
-        description = "Number of days to be included in feeds.";
+        description = lib.mdDoc "Number of days to be included in feeds.";
       };
 
       feedRefreshTime = mkOption {
         type = types.int;
         default = 60;
-        description = "Number of minutes to cache feeds before refreshing.";
+        description = lib.mdDoc "Number of minutes to cache feeds before refreshing.";
       };
 
       pdfExport = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        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.
@@ -524,7 +524,7 @@ video/x-ms-wmx  wmx
       pandocUserData = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        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
@@ -537,7 +537,7 @@ video/x-ms-wmx  wmx
       xssSanitize = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        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.
         '';
@@ -546,37 +546,37 @@ video/x-ms-wmx  wmx
       oauthClientId = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "OAuth client ID";
+        description = lib.mdDoc "OAuth client ID";
       };
 
       oauthClientSecret = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "OAuth client secret";
+        description = lib.mdDoc "OAuth client secret";
       };
 
       oauthCallback = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "OAuth callback URL";
+        description = lib.mdDoc "OAuth callback URL";
       };
 
       oauthAuthorizeEndpoint = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "OAuth authorize endpoint";
+        description = lib.mdDoc "OAuth authorize endpoint";
       };
 
       oauthAccessTokenEndpoint = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "OAuth access token endpoint";
+        description = lib.mdDoc "OAuth access token endpoint";
       };
 
       githubOrg = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = "Github organization";
+        description = lib.mdDoc "Github organization";
       };
   };
 
@@ -689,14 +689,14 @@ in
           ''
           if [ ! -d _darcs ]
           then
-            ${pkgs.darcs}/bin/darcs initialize
+            darcs initialize
             echo "${gm}" > _darcs/prefs/email
           ''
           else if repositoryType == "mercurial" then
           ''
           if [ ! -d .hg ]
           then
-            ${pkgs.mercurial}/bin/hg init
+            hg init
             cat >> .hg/hgrc <<NAMED
 [ui]
 username = gitit ${gm}
@@ -706,9 +706,9 @@ NAMED
           ''
           if [ ! -d  .git ]
           then
-            ${pkgs.git}/bin/git init
-            ${pkgs.git}/bin/git config user.email "${gm}"
-            ${pkgs.git}/bin/git config user.name "gitit"
+            git init
+            git config user.email "${gm}"
+            git config user.name "gitit"
           ''}
           chown ${uid}:${gid} -R ${repositoryPath}
           fi
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 0811b34156e4..e7c707228f1b 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -6,6 +6,9 @@ let
   cfg = config.services.gitlab;
   opt = options.services.gitlab;
 
+  toml = pkgs.formats.toml {};
+  yaml = pkgs.formats.yaml {};
+
   ruby = cfg.packages.gitlab.ruby;
 
   postgresqlPackage = if config.services.postgresql.enable then
@@ -17,8 +20,8 @@ let
   gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
   pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
 
-  databaseConfig = {
-    production = {
+  databaseConfig = let
+    val = {
       adapter = "postgresql";
       database = cfg.databaseName;
       host = cfg.databaseHost;
@@ -26,6 +29,10 @@ let
       encoding = "utf8";
       pool = cfg.databasePool;
     } // cfg.extraDatabaseConfig;
+  in if lib.versionAtLeast (lib.getVersion cfg.packages.gitlab) "15.0" then {
+    production.main = val;
+  } else {
+    production = val;
   };
 
   # We only want to create a database if we're actually going to connect to it.
@@ -69,17 +76,18 @@ 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;
 
+  cableYml = yaml.generate "cable.yml" {
+    production = {
+      adapter = "redis";
+      url = cfg.redisUrl;
+      channel_prefix = "gitlab_production";
+    };
+  };
+
   pagesArgs = [
     "-pages-domain" gitlabConfig.production.pages.host
     "-pages-root" "${gitlabConfig.production.shared.path}/pages"
@@ -148,7 +156,7 @@ 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;
@@ -168,16 +176,27 @@ let
     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'
      '';
@@ -185,14 +204,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'
      '';
   };
@@ -225,6 +244,7 @@ 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")
   ];
 
   options = {
@@ -232,7 +252,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the gitlab service.
         '';
       };
@@ -241,7 +261,7 @@ in {
         type = types.package;
         default = pkgs.gitlab;
         defaultText = literalExpression "pkgs.gitlab";
-        description = "Reference to the gitlab package";
+        description = lib.mdDoc "Reference to the gitlab package";
         example = literalExpression "pkgs.gitlab-ee";
       };
 
@@ -249,48 +269,48 @@ in {
         type = types.package;
         default = pkgs.gitlab-shell;
         defaultText = literalExpression "pkgs.gitlab-shell";
-        description = "Reference to the gitlab-shell package";
+        description = lib.mdDoc "Reference to the gitlab-shell package";
       };
 
       packages.gitlab-workhorse = mkOption {
         type = types.package;
         default = pkgs.gitlab-workhorse;
         defaultText = literalExpression "pkgs.gitlab-workhorse";
-        description = "Reference to the gitlab-workhorse package";
+        description = lib.mdDoc "Reference to the gitlab-workhorse package";
       };
 
       packages.gitaly = mkOption {
         type = types.package;
         default = pkgs.gitaly;
         defaultText = literalExpression "pkgs.gitaly";
-        description = "Reference to the gitaly package";
+        description = lib.mdDoc "Reference to the gitaly package";
       };
 
       packages.pages = mkOption {
         type = types.package;
         default = pkgs.gitlab-pages;
         defaultText = literalExpression "pkgs.gitlab-pages";
-        description = "Reference to the gitlab-pages package";
+        description = lib.mdDoc "Reference to the gitlab-pages package";
       };
 
       statePath = mkOption {
         type = types.str;
         default = "/var/gitlab/state";
-        description = ''
+        description = lib.mdDoc ''
           GitLab state directory. Configuration, repositories and
           logs, among other things, are stored here.
 
           The directory will be created automatically if it doesn't
           exist already. Its parent directories must be owned by
-          either <literal>root</literal> or the user set in
-          <option>services.gitlab.user</option>.
+          either `root` or the user set in
+          {option}`services.gitlab.user`.
         '';
       };
 
       extraEnv = mkOption {
         type = types.attrsOf types.str;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Additional environment variables for the GitLab environment.
         '';
       };
@@ -299,11 +319,10 @@ in {
         type = with types; either str (listOf str);
         default = [];
         example = "03:00";
-        description = ''
+        description = lib.mdDoc ''
           The time(s) to run automatic backup of GitLab
           state. Specified in systemd's time format; see
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
 
@@ -311,7 +330,7 @@ in {
         type = types.str;
         default = cfg.statePath + "/backup";
         defaultText = literalExpression ''config.${opt.statePath} + "/backup"'';
-        description = "GitLab path for backups.";
+        description = lib.mdDoc "GitLab path for backups.";
       };
 
       backup.keepTime = mkOption {
@@ -319,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”.
         '';
       };
 
@@ -344,13 +362,13 @@ in {
         default = [];
         example = [ "artifacts" "lfs" ];
         apply = x: if isString x then x else concatStringsSep "," x;
-        description = ''
+        description = lib.mdDoc ''
           Directories to exclude from the backup. The example excludes
           CI artifacts and LFS objects from the backups. The
-          <literal>tar</literal> option skips the creation of a tar
+          `tar` option skips the creation of a tar
           file.
 
-          Refer to <link xlink:href="https://docs.gitlab.com/ee/raketasks/backup_restore.html#excluding-specific-directories-from-the-backup"/>
+          Refer to <https://docs.gitlab.com/ee/raketasks/backup_restore.html#excluding-specific-directories-from-the-backup>
           for more information.
         '';
       };
@@ -383,29 +401,29 @@ in {
             storage_class = "STANDARD";
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           GitLab automatic upload specification. Tells GitLab to
           upload the backup to a remote location when done.
 
           Attributes specified here are added under
-          <literal>production -> backup -> upload</literal> in
-          <filename>config/gitlab.yml</filename>.
+          `production -> backup -> upload` in
+          {file}`config/gitlab.yml`.
         '';
       };
 
       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”.
         '';
       };
 
       databasePasswordFile = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           File containing the GitLab database user password.
 
           This should be a string, not a nix path, since nix paths are
@@ -416,43 +434,43 @@ in {
       databaseCreateLocally = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether a database should be automatically created on the
-          local host. Set this to <literal>false</literal> if you plan
+          local host. Set this to `false` if you plan
           on provisioning a local database yourself. This has no effect
-          if <option>services.gitlab.databaseHost</option> is customized.
+          if {option}`services.gitlab.databaseHost` is customized.
         '';
       };
 
       databaseName = mkOption {
         type = types.str;
         default = "gitlab";
-        description = "GitLab database name.";
+        description = lib.mdDoc "GitLab database name.";
       };
 
       databaseUsername = mkOption {
         type = types.str;
         default = "gitlab";
-        description = "GitLab database user.";
+        description = lib.mdDoc "GitLab database user.";
       };
 
       databasePool = mkOption {
         type = types.int;
         default = 5;
-        description = "Database connection pool size.";
+        description = lib.mdDoc "Database connection pool size.";
       };
 
       extraDatabaseConfig = mkOption {
         type = types.attrs;
         default = {};
-        description = "Extra configuration in config/database.yml.";
+        description = lib.mdDoc "Extra configuration in config/database.yml.";
       };
 
       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 = "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 {
@@ -468,7 +486,7 @@ in {
             }
           end
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to be placed in config/extra-gitlab.rb. This can
           be used to add configuration not otherwise exposed through this module's
           options.
@@ -479,13 +497,13 @@ in {
         type = types.str;
         default = config.networking.hostName;
         defaultText = literalExpression "config.networking.hostName";
-        description = "GitLab host name. Used e.g. for copy-paste URLs.";
+        description = lib.mdDoc "GitLab host name. Used e.g. for copy-paste URLs.";
       };
 
       port = mkOption {
         type = types.port;
         default = 8080;
-        description = ''
+        description = lib.mdDoc ''
           GitLab server port for copy-paste URLs, e.g. 80 or 443 if you're
           service over https.
         '';
@@ -494,25 +512,25 @@ in {
       https = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether gitlab prints URLs with https as scheme.";
+        description = lib.mdDoc "Whether gitlab prints URLs with https as scheme.";
       };
 
       user = mkOption {
         type = types.str;
         default = "gitlab";
-        description = "User to run gitlab and all related services.";
+        description = lib.mdDoc "User to run gitlab and all related services.";
       };
 
       group = mkOption {
         type = types.str;
         default = "gitlab";
-        description = "Group to run gitlab and all related services.";
+        description = lib.mdDoc "Group to run gitlab and all related services.";
       };
 
       initialRootEmail = mkOption {
         type = types.str;
         default = "admin@local.host";
-        description = ''
+        description = lib.mdDoc ''
           Initial email address of the root account if this is a new install.
         '';
       };
@@ -520,7 +538,7 @@ in {
       initialRootPasswordFile = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           File containing the initial password of the root account if
           this is a new install.
 
@@ -533,51 +551,51 @@ in {
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = "Enable GitLab container registry.";
+          description = lib.mdDoc "Enable GitLab container registry.";
         };
         host = mkOption {
           type = types.str;
           default = config.services.gitlab.host;
           defaultText = literalExpression "config.services.gitlab.host";
-          description = "GitLab container registry host name.";
+          description = lib.mdDoc "GitLab container registry host name.";
         };
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 4567;
-          description = "GitLab container registry port.";
+          description = lib.mdDoc "GitLab container registry port.";
         };
         certFile = mkOption {
           type = types.path;
-          description = "Path to GitLab container registry certificate.";
+          description = lib.mdDoc "Path to GitLab container registry certificate.";
         };
         keyFile = mkOption {
           type = types.path;
-          description = "Path to GitLab container registry certificate-key.";
+          description = lib.mdDoc "Path to GitLab container registry certificate-key.";
         };
         defaultForProjects = mkOption {
           type = types.bool;
           default = cfg.registry.enable;
           defaultText = literalExpression "config.${opt.registry.enable}";
-          description = "If GitLab container registry should be enabled by default for projects.";
+          description = lib.mdDoc "If GitLab container registry should be enabled by default for projects.";
         };
         issuer = mkOption {
           type = types.str;
           default = "gitlab-issuer";
-          description = "GitLab container registry issuer.";
+          description = lib.mdDoc "GitLab container registry issuer.";
         };
         serviceName = mkOption {
           type = types.str;
           default = "container_registry";
-          description = "GitLab container registry service name.";
+          description = lib.mdDoc "GitLab container registry service name.";
         };
         externalAddress = mkOption {
           type = types.str;
           default = "";
-          description = "External address used to access registry from the internet";
+          description = lib.mdDoc "External address used to access registry from the internet";
         };
         externalPort = mkOption {
           type = types.int;
-          description = "External port used to access registry from the internet";
+          description = lib.mdDoc "External port used to access registry from the internet";
         };
       };
 
@@ -585,31 +603,31 @@ in {
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = "Enable gitlab mail delivery over SMTP.";
+          description = lib.mdDoc "Enable gitlab mail delivery over SMTP.";
         };
 
         address = mkOption {
           type = types.str;
           default = "localhost";
-          description = "Address of the SMTP server for GitLab.";
+          description = lib.mdDoc "Address of the SMTP server for GitLab.";
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 25;
-          description = "Port of the SMTP server for GitLab.";
+          description = lib.mdDoc "Port of the SMTP server for GitLab.";
         };
 
         username = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = "Username of the SMTP server for GitLab.";
+          description = lib.mdDoc "Username of the SMTP server for GitLab.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             File containing the password of the SMTP server for GitLab.
 
             This should be a string, not a nix path, since nix paths
@@ -620,44 +638,44 @@ in {
         domain = mkOption {
           type = types.str;
           default = "localhost";
-          description = "HELO domain to use for outgoing mail.";
+          description = lib.mdDoc "HELO domain to use for outgoing mail.";
         };
 
         authentication = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+          description = lib.mdDoc "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
         };
 
         enableStartTLSAuto = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether to try to use StartTLS.";
+          description = lib.mdDoc "Whether to try to use StartTLS.";
         };
 
         tls = mkOption {
           type = types.bool;
           default = false;
-          description = "Whether to use TLS wrapper-mode.";
+          description = lib.mdDoc "Whether to use TLS wrapper-mode.";
         };
 
         opensslVerifyMode = mkOption {
           type = types.str;
           default = "peer";
-          description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+          description = lib.mdDoc "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
         };
       };
 
       pagesExtraArgs = mkOption {
         type = types.listOf types.str;
         default = [ "-listen-proxy" "127.0.0.1:8090" ];
-        description = "Arguments to pass to the gitlab-pages daemon";
+        description = lib.mdDoc "Arguments to pass to the gitlab-pages daemon";
       };
 
       secrets.secretFile = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A file containing the secret used to encrypt variables in
           the DB. If you change or lose this key you will be unable to
           access variables stored in database.
@@ -673,7 +691,7 @@ in {
       secrets.dbFile = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A file containing the secret used to encrypt variables in
           the DB. If you change or lose this key you will be unable to
           access variables stored in database.
@@ -689,7 +707,7 @@ in {
       secrets.otpFile = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A file containing the secret used to encrypt secrets for OTP
           tokens. If you change or lose this key, users which have 2FA
           enabled for login won't be able to login anymore.
@@ -705,7 +723,7 @@ in {
       secrets.jwsFile = mkOption {
         type = with types; nullOr path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A file containing the secret used to encrypt session
           keys. If you change or lose this key, users will be
           disconnected.
@@ -723,25 +741,22 @@ in {
       extraShellConfig = mkOption {
         type = types.attrs;
         default = {};
-        description = "Extra configuration to merge into shell-config.yml";
+        description = lib.mdDoc "Extra configuration to merge into shell-config.yml";
       };
 
       puma.workers = mkOption {
         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.
+          :::
         '';
       };
 
@@ -749,16 +764,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.
+          :::
         '';
       };
 
@@ -766,31 +779,29 @@ 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.
+          :::
         '';
       };
 
       sidekiq.memoryKiller.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the Sidekiq MemoryKiller should be turned
           on. MemoryKiller kills Sidekiq when its memory consumption
           exceeds a certain limit.
 
-          See <link xlink:href="https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html"/>
+          See <https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html>
           for details.
         '';
       };
@@ -799,7 +810,7 @@ in {
         type = types.int;
         default = 2000;
         apply = x: builtins.toString (x * 1024);
-        description = ''
+        description = lib.mdDoc ''
           The maximum amount of memory, in MiB, a Sidekiq worker is
           allowed to consume before being killed.
         '';
@@ -809,7 +820,7 @@ in {
         type = types.int;
         default = 900;
         apply = x: builtins.toString x;
-        description = ''
+        description = lib.mdDoc ''
           The time MemoryKiller waits after noticing excessive memory
           consumption before killing Sidekiq.
         '';
@@ -819,7 +830,7 @@ in {
         type = types.int;
         default = 30;
         apply = x: builtins.toString x;
-        description = ''
+        description = lib.mdDoc ''
           The time allowed for all jobs to finish before Sidekiq is
           killed forcefully.
         '';
@@ -829,7 +840,7 @@ in {
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Enable rotation of log files.
           '';
         };
@@ -837,27 +848,51 @@ in {
         frequency = mkOption {
           type = types.str;
           default = "daily";
-          description = "How often to rotate the logs.";
+          description = lib.mdDoc "How often to rotate the logs.";
         };
 
         keep = mkOption {
           type = types.int;
           default = 30;
-          description = "How many rotations to keep.";
+          description = lib.mdDoc "How many rotations to keep.";
         };
+      };
 
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
-          description = ''
-            Extra logrotate config options for this path. Refer to
-            <link xlink:href="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 ''
           {
@@ -894,21 +929,21 @@ in {
             };
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra options to be added under
-          <literal>production</literal> in
-          <filename>config/gitlab.yml</filename>, as a nix attribute
+          `production` in
+          {file}`config/gitlab.yml`, as a nix attribute
           set.
 
           Options containing secret data should be set to an attribute
-          set containing the attribute <literal>_secret</literal> - a
+          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
-          <filename>config/gitlab.yml</filename> file, the
-          <literal>production.omniauth.providers[0].args.client_options.secret</literal>
+          {file}`config/gitlab.yml` file, the
+          `production.omniauth.providers[0].args.client_options.secret`
           key will be set to the contents of the
-          <filename>/var/keys/gitlab_oidc_secret</filename> file.
+          {file}`/var/keys/gitlab_oidc_secret` file.
         '';
       };
     };
@@ -961,8 +996,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.
@@ -982,7 +1018,6 @@ in {
           rotate = cfg.logrotate.keep;
           copytruncate = true;
           compress = true;
-          extraConfig = cfg.logrotate.extraConfig;
         };
       };
     };
@@ -1043,7 +1078,7 @@ in {
         chown ${cfg.user}:${cfg.group} ${cfg.registry.certFile}
       '';
 
-      serviceConfig = {
+      unitConfig = {
         ConditionPathExists = "!${cfg.registry.certFile}";
       };
     };
@@ -1051,6 +1086,7 @@ 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
@@ -1104,6 +1140,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"
@@ -1157,6 +1194,7 @@ 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
 
           ${cfg.packages.gitlab-shell}/bin/install
 
@@ -1184,7 +1222,7 @@ in {
                 fi
 
                 jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
-                   '.production.password = $ENV.db_password' \
+                   '.${if lib.versionAtLeast (lib.getVersion cfg.packages.gitlab) "15.0" then "production.main" else "production"}.password = $ENV.db_password' \
                    >'${cfg.statePath}/config/database.yml'
               ''
               else ''
@@ -1346,6 +1384,7 @@ in {
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
       path = with pkgs; [
+        remarshal
         exiftool
         git
         gnutar
@@ -1360,6 +1399,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 "
@@ -1367,6 +1417,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";
       };
     };
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
index 40424c5039a2..9816fdac7dd7 100644
--- a/nixos/modules/services/misc/gitlab.xml
+++ b/nixos/modules/services/misc/gitlab.xml
@@ -141,7 +141,7 @@ services.gitlab = {
    </para>
 
    <para>
-    A list of all availabe rake tasks can be obtained by running:
+    A list of all available rake tasks can be obtained by running:
 <screen>
 <prompt>$ </prompt>sudo -u git -H gitlab-rake -T
 </screen>
diff --git a/nixos/modules/services/misc/gitolite.nix b/nixos/modules/services/misc/gitolite.nix
index 810ef1f21b9c..012abda2d76f 100644
--- a/nixos/modules/services/misc/gitolite.nix
+++ b/nixos/modules/services/misc/gitolite.nix
@@ -14,19 +14,18 @@ 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.
         '';
       };
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/gitolite";
-        description = ''
+        description = lib.mdDoc ''
           The gitolite home directory used to store all repositories. If left as the default value
           this directory will automatically be created before the gitolite server starts, otherwise
           the sysadmin is responsible for ensuring the directory exists with appropriate ownership
@@ -36,7 +35,7 @@ in
 
       adminPubkey = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Initial administrative public key for Gitolite. This should
           be an SSH Public Key. Note that this key will only be used
           once, upon the first initialization of the Gitolite user.
@@ -47,8 +46,8 @@ in
       enableGitAnnex = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Enable git-annex support. Uses the <literal>extraGitoliteRc</literal> option
+        description = lib.mdDoc ''
+          Enable git-annex support. Uses the `extraGitoliteRc` option
           to apply the necessary configuration.
         '';
       };
@@ -56,8 +55,8 @@ in
       commonHooks = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
-          A list of custom git hooks that get copied to <literal>~/.gitolite/hooks/common</literal>.
+        description = lib.mdDoc ''
+          A list of custom git hooks that get copied to `~/.gitolite/hooks/common`.
         '';
       };
 
@@ -72,40 +71,48 @@ 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.
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "gitolite";
-        description = ''
+        description = lib.mdDoc ''
           Gitolite user account. This is the username of the gitolite endpoint.
         '';
       };
 
+      description = mkOption {
+        type = types.str;
+        default = "Gitolite user";
+        description = lib.mdDoc ''
+          Gitolite user account's description.
+        '';
+      };
+
       group = mkOption {
         type = types.str;
         default = "gitolite";
-        description = ''
+        description = lib.mdDoc ''
           Primary group of the Gitolite user account.
         '';
       };
@@ -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/nixos/modules/services/misc/gitweb.nix b/nixos/modules/services/misc/gitweb.nix
index a1180716e36b..aac0dac8a080 100644
--- a/nixos/modules/services/misc/gitweb.nix
+++ b/nixos/modules/services/misc/gitweb.nix
@@ -13,7 +13,7 @@ in
     projectroot = mkOption {
       default = "/srv/git";
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to git projects (bare repositories) that should be served by
         gitweb. Must not end with a slash.
       '';
@@ -22,7 +22,7 @@ in
     extraConfig = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Verbatim configuration text appended to the generated gitweb.conf file.
       '';
       example = ''
@@ -35,7 +35,7 @@ in
     gitwebTheme = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Use an alternative theme for gitweb, strongly inspired by GitHub.
       '';
     };
@@ -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/nixos/modules/services/misc/gogs.nix b/nixos/modules/services/misc/gogs.nix
index c7ae4f494071..fa172ed277dc 100644
--- a/nixos/modules/services/misc/gogs.nix
+++ b/nixos/modules/services/misc/gogs.nix
@@ -48,31 +48,31 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "Enable Go Git Service.";
+        description = lib.mdDoc "Enable Go Git Service.";
       };
 
       useWizard = mkOption {
         default = false;
         type = types.bool;
-        description = "Do not generate a configuration and use Gogs' installation wizard instead. The first registered user will be administrator.";
+        description = lib.mdDoc "Do not generate a configuration and use Gogs' installation wizard instead. The first registered user will be administrator.";
       };
 
       stateDir = mkOption {
         default = "/var/lib/gogs";
         type = types.str;
-        description = "Gogs data directory.";
+        description = lib.mdDoc "Gogs data directory.";
       };
 
       user = mkOption {
         type = types.str;
         default = "gogs";
-        description = "User account under which Gogs runs.";
+        description = lib.mdDoc "User account under which Gogs runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "gogs";
-        description = "Group account under which Gogs runs.";
+        description = lib.mdDoc "Group account under which Gogs runs.";
       };
 
       database = {
@@ -80,40 +80,40 @@ in
           type = types.enum [ "sqlite3" "mysql" "postgres" ];
           example = "mysql";
           default = "sqlite3";
-          description = "Database engine to use.";
+          description = lib.mdDoc "Database engine to use.";
         };
 
         host = mkOption {
           type = types.str;
           default = "127.0.0.1";
-          description = "Database host address.";
+          description = lib.mdDoc "Database host address.";
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 3306;
-          description = "Database host port.";
+          description = lib.mdDoc "Database host port.";
         };
 
         name = mkOption {
           type = types.str;
           default = "gogs";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = mkOption {
           type = types.str;
           default = "gogs";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         password = mkOption {
           type = types.str;
           default = "";
-          description = ''
-            The password corresponding to <option>database.user</option>.
+          description = lib.mdDoc ''
+            The password corresponding to {option}`database.user`.
             Warning: this is stored in cleartext in the Nix store!
-            Use <option>database.passwordFile</option> instead.
+            Use {option}`database.passwordFile` instead.
           '';
         };
 
@@ -121,9 +121,9 @@ in
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/gogs-dbpassword";
-          description = ''
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
 
@@ -131,51 +131,51 @@ in
           type = types.str;
           default = "${cfg.stateDir}/data/gogs.db";
           defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/gogs.db"'';
-          description = "Path to the sqlite3 database file.";
+          description = lib.mdDoc "Path to the sqlite3 database file.";
         };
       };
 
       appName = mkOption {
         type = types.str;
         default = "Gogs: Go Git Service";
-        description = "Application name.";
+        description = lib.mdDoc "Application name.";
       };
 
       repositoryRoot = mkOption {
         type = types.str;
         default = "${cfg.stateDir}/repositories";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
-        description = "Path to the git repositories.";
+        description = lib.mdDoc "Path to the git repositories.";
       };
 
       domain = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Domain name of your server.";
+        description = lib.mdDoc "Domain name of your server.";
       };
 
       rootUrl = mkOption {
         type = types.str;
         default = "http://localhost:3000/";
-        description = "Full public URL of Gogs server.";
+        description = lib.mdDoc "Full public URL of Gogs server.";
       };
 
       httpAddress = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "HTTP listen address.";
+        description = lib.mdDoc "HTTP listen address.";
       };
 
       httpPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
-        description = "HTTP listen port.";
+        description = lib.mdDoc "HTTP listen port.";
       };
 
       cookieSecure = 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 Gogs is being served over HTTPS.
         '';
@@ -184,7 +184,7 @@ in
       extraConfig = mkOption {
         type = types.str;
         default = "";
-        description = "Configuration lines appended to the generated Gogs configuration file.";
+        description = lib.mdDoc "Configuration lines appended to the generated Gogs configuration file.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index cad73a871ba6..4eec9610b5e9 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -8,67 +8,89 @@ in
 
 {
   options.services.gollum = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = "Enable the Gollum service.";
-    };
+    enable = mkEnableOption (lib.mdDoc "Gollum service");
 
     address = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = "IP address on which the web server will listen.";
+      description = lib.mdDoc "IP address on which the web server will listen.";
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 4567;
-      description = "Port on which the web server will run.";
+      description = lib.mdDoc "Port on which the web server will run.";
     };
 
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = "Content of the configuration file";
+      description = lib.mdDoc "Content of the configuration file";
     };
 
     mathjax = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable support for math rendering using MathJax";
+      description = lib.mdDoc "Enable support for math rendering using MathJax";
     };
 
     allowUploads = mkOption {
       type = types.nullOr (types.enum [ "dir" "page" ]);
       default = null;
-      description = "Enable uploads of external files";
+      description = lib.mdDoc "Enable uploads of external files";
+    };
+
+    user-icons = mkOption {
+      type = types.nullOr (types.enum [ "gravatar" "identicon" ]);
+      default = null;
+      description = lib.mdDoc "Enable specific user icons for history view";
     };
 
     emoji = mkOption {
       type = types.bool;
       default = false;
-      description = "Parse and interpret emoji tags";
+      description = lib.mdDoc "Parse and interpret emoji tags";
     };
 
     h1-title = mkOption {
       type = types.bool;
       default = false;
-      description = "Use the first h1 as page title";
+      description = lib.mdDoc "Use the first h1 as page title";
+    };
+
+    no-edit = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "Disable editing pages";
+    };
+
+    local-time = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc "Use the browser's local timezone instead of the server's for displaying dates.";
     };
 
     branch = mkOption {
       type = types.str;
       default = "master";
       example = "develop";
-      description = "Git branch to serve";
+      description = lib.mdDoc "Git branch to serve";
     };
 
     stateDir = mkOption {
       type = types.path;
       default = "/var/lib/gollum";
-      description = "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup.";
+      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 {
@@ -102,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} \
@@ -110,12 +132,15 @@ in
             ${optionalString cfg.mathjax "--mathjax"} \
             ${optionalString cfg.emoji "--emoji"} \
             ${optionalString cfg.h1-title "--h1-title"} \
+            ${optionalString cfg.no-edit "--no-edit"} \
+            ${optionalString cfg.local-time "--local-time"} \
             ${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \
+            ${optionalString (cfg.user-icons != null) "--user-icons ${cfg.user-icons}"} \
             ${cfg.stateDir}
         '';
       };
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ erictapen ];
+  meta.maintainers = with lib.maintainers; [ erictapen bbenno ];
 }
diff --git a/nixos/modules/services/misc/gpsd.nix b/nixos/modules/services/misc/gpsd.nix
index 6494578f7647..ec0a8e1eaa1c 100644
--- a/nixos/modules/services/misc/gpsd.nix
+++ b/nixos/modules/services/misc/gpsd.nix
@@ -21,7 +21,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable `gpsd', a GPS service daemon.
         '';
       };
@@ -29,9 +29,9 @@ in
       device = mkOption {
         type = types.str;
         default = "/dev/ttyUSB0";
-        description = ''
+        description = lib.mdDoc ''
           A device may be a local serial device for GPS input, or a URL of the form:
-               <literal>[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]</literal>
+               `[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]`
           in which case it specifies an input source for DGPS or ntrip data.
         '';
       };
@@ -39,7 +39,7 @@ in
       readonly = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the broken-device-safety, otherwise
           known as read-only mode.  Some popular bluetooth and USB
           receivers lock up or become totally inaccessible when
@@ -56,7 +56,7 @@ in
       nowait = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           don't wait for client connects to poll GPS
         '';
       };
@@ -64,7 +64,7 @@ in
       port = mkOption {
         type = types.port;
         default = 2947;
-        description = ''
+        description = lib.mdDoc ''
           The port where to listen for TCP connections.
         '';
       };
@@ -72,11 +72,19 @@ in
       debugLevel = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           The debugging level.
         '';
       };
 
+      listenany = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Listen on all addresses rather than just loopback.
+        '';
+      };
+
     };
 
   };
@@ -106,6 +114,7 @@ in
             -S "${toString cfg.port}"                             \
             ${optionalString cfg.readonly "-b"}                   \
             ${optionalString cfg.nowait "-n"}                     \
+            ${optionalString cfg.listenany "-G"}                  \
             "${cfg.device}"
         '';
       };
diff --git a/nixos/modules/services/misc/greenclip.nix b/nixos/modules/services/misc/greenclip.nix
index 32e8d746cb5c..45847af71141 100644
--- a/nixos/modules/services/misc/greenclip.nix
+++ b/nixos/modules/services/misc/greenclip.nix
@@ -7,13 +7,13 @@ let
 in {
 
   options.services.greenclip = {
-    enable = mkEnableOption "Greenclip daemon";
+    enable = mkEnableOption (lib.mdDoc "Greenclip daemon");
 
     package = mkOption {
       type = types.package;
       default = pkgs.haskellPackages.greenclip;
       defaultText = literalExpression "pkgs.haskellPackages.greenclip";
-      description = "greenclip derivation to use.";
+      description = lib.mdDoc "greenclip derivation to use.";
     };
   };
 
diff --git a/nixos/modules/services/misc/headphones.nix b/nixos/modules/services/misc/headphones.nix
index 31bd61cb4c20..472b330fff15 100644
--- a/nixos/modules/services/misc/headphones.nix
+++ b/nixos/modules/services/misc/headphones.nix
@@ -20,38 +20,38 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the headphones server.";
+        description = lib.mdDoc "Whether to enable the headphones server.";
       };
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/${name}";
-        description = "Path where to store data files.";
+        description = lib.mdDoc "Path where to store data files.";
       };
       configFile = mkOption {
         type = types.path;
         default = "${cfg.dataDir}/config.ini";
         defaultText = literalExpression ''"''${config.${opt.dataDir}}/config.ini"'';
-        description = "Path to config file.";
+        description = lib.mdDoc "Path to config file.";
       };
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Host to listen on.";
+        description = lib.mdDoc "Host to listen on.";
       };
       port = mkOption {
         type = types.ints.u16;
         default = 8181;
-        description = "Port to bind to.";
+        description = lib.mdDoc "Port to bind to.";
       };
       user = mkOption {
         type = types.str;
         default = name;
-        description = "User to run the service as";
+        description = lib.mdDoc "User to run the service as";
       };
       group = mkOption {
         type = types.str;
         default = name;
-        description = "Group to run the service as";
+        description = lib.mdDoc "Group to run the service as";
       };
     };
   };
diff --git a/nixos/modules/services/misc/heisenbridge.nix b/nixos/modules/services/misc/heisenbridge.nix
index 7ce8a23d9af1..d07e0e420462 100644
--- a/nixos/modules/services/misc/heisenbridge.nix
+++ b/nixos/modules/services/misc/heisenbridge.nix
@@ -23,27 +23,26 @@ 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 { … = …; }";
-      description = ''
+      defaultText = lib.literalExpression "pkgs.heisenbridge";
+      description = lib.mdDoc ''
         Package of the application to run, exposed for overriding purposes.
       '';
     };
 
     homeserver = mkOption {
       type = types.str;
-      description = "The URL to the home server for client-server API calls";
+      description = lib.mdDoc "The URL to the home server for client-server API calls";
       example = "http://localhost:8008";
     };
 
     registrationUrl = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The URL where the application service is listening for HS requests, from the Matrix HS perspective.#
         The default value assumes the bridge runs on the same host as the home server, in the same network.
       '';
@@ -54,26 +53,26 @@ in
 
     address = mkOption {
       type = types.str;
-      description = "Address to listen on. IPv6 does not seem to be supported.";
+      description = lib.mdDoc "Address to listen on. IPv6 does not seem to be supported.";
       default = "127.0.0.1";
       example = "0.0.0.0";
     };
 
     port = mkOption {
       type = types.port;
-      description = "The port to listen on";
+      description = lib.mdDoc "The port to listen on";
       default = 9898;
     };
 
     debug = mkOption {
       type = types.bool;
-      description = "More verbose logging. Recommended during initial setup.";
+      description = lib.mdDoc "More verbose logging. Recommended during initial setup.";
       default = false;
     };
 
     owner = mkOption {
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         Set owner MXID otherwise first talking local user will claim the bridge
       '';
       default = null;
@@ -81,7 +80,7 @@ in
     };
 
     namespaces = mkOption {
-      description = "Configure the 'namespaces' section of the registration.yml for the bridge and the server";
+      description = lib.mdDoc "Configure the 'namespaces' section of the registration.yml for the bridge and the server";
       # TODO link to Matrix documentation of the format
       type = types.submodule {
         freeformType = jsonType;
@@ -99,16 +98,16 @@ in
       };
     };
 
-    identd.enable = mkEnableOption "identd service support";
+    identd.enable = mkEnableOption (lib.mdDoc "identd service support");
     identd.port = mkOption {
       type = types.port;
-      description = "identd listen port";
+      description = lib.mdDoc "identd listen port";
       default = 113;
     };
 
     extraArgs = mkOption {
       type = types.listOf types.str;
-      description = "Heisenbridge is configured over the command line. Append extra arguments here";
+      description = lib.mdDoc "Heisenbridge is configured over the command line. Append extra arguments here";
       default = [ ];
     };
   };
@@ -204,7 +203,7 @@ in
         NoNewPrivileges = true;
         LockPersonality = true;
         RestrictRealtime = true;
-        SystemCallFilter = ["@system-service" "~@priviledged" "@chown"];
+        SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
         SystemCallArchitectures = "native";
         RestrictAddressFamilies = "AF_INET AF_INET6";
       };
diff --git a/nixos/modules/services/misc/ihaskell.nix b/nixos/modules/services/misc/ihaskell.nix
index 9978e8a46534..4782053c4fb8 100644
--- a/nixos/modules/services/misc/ihaskell.nix
+++ b/nixos/modules/services/misc/ihaskell.nix
@@ -17,7 +17,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Autostart an IHaskell notebook service.";
+        description = lib.mdDoc "Autostart an IHaskell notebook service.";
       };
 
       extraPackages = mkOption {
@@ -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/nixos/modules/services/misc/input-remapper.nix b/nixos/modules/services/misc/input-remapper.nix
index f5fb2bf53086..51e1abdc98a0 100644
--- a/nixos/modules/services/misc/input-remapper.nix
+++ b/nixos/modules/services/misc/input-remapper.nix
@@ -6,14 +6,14 @@ 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.";
+      enable = mkEnableOption (lib.mdDoc "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";
+      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" ];
         type = types.listOf types.str;
-        description = "Specifies the WantedBy setting for the input-remapper service.";
+        description = lib.mdDoc "Specifies the WantedBy setting for the input-remapper service.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/irkerd.nix b/nixos/modules/services/misc/irkerd.nix
index 993d77ba424c..d080cc0a7358 100644
--- a/nixos/modules/services/misc/irkerd.nix
+++ b/nixos/modules/services/misc/irkerd.nix
@@ -9,13 +9,13 @@ in
 {
   options.services.irkerd = {
     enable = mkOption {
-      description = "Whether to enable irker, an IRC notification daemon.";
+      description = lib.mdDoc "Whether to enable irker, an IRC notification daemon.";
       default = false;
       type = types.bool;
     };
 
     openPorts = mkOption {
-      description = "Open ports in the firewall for irkerd";
+      description = lib.mdDoc "Open ports in the firewall for irkerd";
       default = false;
       type = types.bool;
     };
@@ -24,7 +24,7 @@ in
       default = "localhost";
       example = "0.0.0.0";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Specifies the bind address on which the irker daemon listens.
         The default is localhost.
 
@@ -36,7 +36,7 @@ in
     nick = mkOption {
       default = "irker";
       type = types.str;
-      description = "Nick to use for irker";
+      description = lib.mdDoc "Nick to use for irker";
     };
   };
 
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index c2144d4a9a9f..b0edf0d18da4 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -9,37 +9,37 @@ in
 {
   options = {
     services.jackett = {
-      enable = mkEnableOption "Jackett";
+      enable = mkEnableOption (lib.mdDoc "Jackett");
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/jackett/.config/Jackett";
-        description = "The directory where Jackett stores its data files.";
+        description = lib.mdDoc "The directory where Jackett stores its data files.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the Jackett web interface.";
+        description = lib.mdDoc "Open ports in the firewall for the Jackett web interface.";
       };
 
       user = mkOption {
         type = types.str;
         default = "jackett";
-        description = "User account under which Jackett runs.";
+        description = lib.mdDoc "User account under which Jackett runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "jackett";
-        description = "Group under which Jackett runs.";
+        description = lib.mdDoc "Group under which Jackett runs.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.jackett;
         defaultText = literalExpression "pkgs.jackett";
-        description = "Jackett package to use.";
+        description = lib.mdDoc "Jackett package to use.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
index 04cf82f8a46b..2a4483199d7d 100644
--- a/nixos/modules/services/misc/jellyfin.nix
+++ b/nixos/modules/services/misc/jellyfin.nix
@@ -8,19 +8,19 @@ in
 {
   options = {
     services.jellyfin = {
-      enable = mkEnableOption "Jellyfin Media Server";
+      enable = mkEnableOption (lib.mdDoc "Jellyfin Media Server");
 
       user = mkOption {
         type = types.str;
         default = "jellyfin";
-        description = "User account under which Jellyfin runs.";
+        description = lib.mdDoc "User account under which Jellyfin runs.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.jellyfin;
         defaultText = literalExpression "pkgs.jellyfin";
-        description = ''
+        description = lib.mdDoc ''
           Jellyfin package to use.
         '';
       };
@@ -28,13 +28,13 @@ in
       group = mkOption {
         type = types.str;
         default = "jellyfin";
-        description = "Group under which jellyfin runs.";
+        description = lib.mdDoc "Group under which jellyfin runs.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open the default ports in the firewall for the media server. The
           HTTP/HTTPS ports can be changed in the Web UI, so this option should
           only be used if they are unchanged.
@@ -49,53 +49,61 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
+      # This is mostly follows: https://github.com/jellyfin/jellyfin/blob/master/fedora/jellyfin.service
+      # Upstream also disable some hardenings when running in LXC, we do the same with the isContainer option
       serviceConfig = rec {
+        Type = "simple";
         User = cfg.user;
         Group = cfg.group;
         StateDirectory = "jellyfin";
+        StateDirectoryMode = "0700";
         CacheDirectory = "jellyfin";
+        CacheDirectoryMode = "0700";
+        UMask = "0077";
+        WorkingDirectory = "/var/lib/jellyfin";
         ExecStart = "${cfg.package}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
         Restart = "on-failure";
+        TimeoutSec = 15;
+        SuccessExitStatus = ["0" "143"];
 
         # Security options:
-
         NoNewPrivileges = true;
-
-        AmbientCapabilities = "";
-        CapabilityBoundingSet = "";
-
-        # ProtectClock= adds DeviceAllow=char-rtc r
-        DeviceAllow = "";
-
-        LockPersonality = true;
-
-        PrivateTmp = true;
-        # Disabled to allow Jellyfin to access hw accel devices endpoints
-        # PrivateDevices = true;
-        PrivateUsers = true;
-
-        # Disabled as it does not allow Jellyfin to interface with CUDA devices
-        # ProtectClock = true;
-        ProtectControlGroups = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-
-        RemoveIPC = true;
-
-        RestrictNamespaces = true;
+        SystemCallArchitectures = "native";
         # AF_NETLINK needed because Jellyfin monitors the network connection
-        RestrictAddressFamilies = [ "AF_NETLINK" "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+        RestrictNamespaces = !config.boot.isContainer;
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
+        ProtectControlGroups = !config.boot.isContainer;
+        ProtectHostname = true;
+        ProtectKernelLogs = !config.boot.isContainer;
+        ProtectKernelModules = !config.boot.isContainer;
+        ProtectKernelTunables = !config.boot.isContainer;
+        LockPersonality = true;
+        PrivateTmp = !config.boot.isContainer;
+        # needed for hardware acceleration
+        PrivateDevices = false;
+        PrivateUsers = true;
+        RemoveIPC = true;
 
-        SystemCallArchitectures = "native";
-        SystemCallErrorNumber = "EPERM";
         SystemCallFilter = [
-          "@system-service"
-          "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
+          "~@clock"
+          "~@aio"
+          "~@chown"
+          "~@cpu-emulation"
+          "~@debug"
+          "~@keyring"
+          "~@memlock"
+          "~@module"
+          "~@mount"
+          "~@obsolete"
+          "~@privileged"
+          "~@raw-io"
+          "~@reboot"
+          "~@setuid"
+          "~@swap"
         ];
+        SystemCallErrorNumber = "EPERM";
       };
     };
 
diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix
index 7b3780b5cc9f..a2158e9461bc 100644
--- a/nixos/modules/services/misc/klipper.nix
+++ b/nixos/modules/services/misc/klipper.nix
@@ -5,46 +5,70 @@ let
   format = pkgs.formats.ini {
     # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996
     listToValue = l:
-      if builtins.length l == 1 then generators.mkValueStringDefault {} (head l)
+      if builtins.length l == 1 then generators.mkValueStringDefault { } (head l)
       else lib.concatMapStrings (s: "\n  ${generators.mkValueStringDefault {} s}") l;
-    mkKeyValue = generators.mkKeyValueDefault {} ":";
+    mkKeyValue = generators.mkKeyValueDefault { } ":";
   };
 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;
         default = pkgs.klipper;
         defaultText = literalExpression "pkgs.klipper";
-        description = "The Klipper package.";
+        description = lib.mdDoc "The Klipper package.";
       };
 
       inputTTY = mkOption {
         type = types.path;
         default = "/run/klipper/tty";
-        description = "Path of the virtual printer symlink to create.";
+        description = lib.mdDoc "Path of the virtual printer symlink to create.";
       };
 
       apiSocket = mkOption {
         type = types.nullOr types.path;
         default = "/run/klipper/api";
-        description = "Path of the API socket to create.";
+        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;
-        description = "Allows Octoprint to control Klipper.";
+        description = lib.mdDoc "Allows Octoprint to control Klipper.";
       };
 
       user = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           User account under which Klipper runs.
 
           If null is specified (default), a temporary user will be created by systemd.
@@ -54,7 +78,7 @@ in
       group = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Group account under which Klipper runs.
 
           If null is specified (default), a temporary user will be created by systemd.
@@ -62,13 +86,35 @@ in
       };
 
       settings = mkOption {
-        type = format.type;
-        default = { };
-        description = ''
-          Configuration for Klipper. See the <link xlink:href="https://www.klipper3d.org/Overview.html#configuration-and-tuning-guides">documentation</link>
+        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.
         '';
       };
+
+      firmwares = mkOption {
+        description = lib.mdDoc "Firmwares klipper should manage";
+        default = { };
+        type = with types; attrsOf
+          (submodule {
+            options = {
+              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 = lib.mdDoc "Path to firmware config which is generated using `klipper-genconf`";
+              };
+            };
+          });
+      };
     };
   };
 
@@ -77,41 +123,102 @@ 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 = 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.defaultConfig.";
       }
     ];
 
-    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;
       group = config.services.octoprint.group;
     };
 
-    systemd.services.klipper = let
-      klippyArgs = "--input-tty=${cfg.inputTTY}"
-        + optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}";
-    in {
-      description = "Klipper 3D Printer Firmware";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
-
-      serviceConfig = {
-        ExecStart = "${cfg.package}/lib/klipper/klippy.py ${klippyArgs} /etc/klipper.cfg";
-        RuntimeDirectory = "klipper";
-        SupplementaryGroups = [ "dialout" ];
-        WorkingDirectory = "${cfg.package}/lib";
-      } // (if cfg.user != null then {
-        Group = cfg.group;
-        User = cfg.user;
-      } else {
-        DynamicUser = true;
-        User = "klipper";
-      });
-    };
+    systemd.services.klipper =
+      let
+        klippyArgs = "--input-tty=${cfg.inputTTY}"
+          + optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}";
+        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} ${printerConfigPath}";
+          RuntimeDirectory = "klipper";
+          StateDirectory = "klipper";
+          SupplementaryGroups = [ "dialout" ];
+          WorkingDirectory = "${cfg.package}/lib";
+          OOMScoreAdjust = "-999";
+          CPUSchedulingPolicy = "rr";
+          CPUSchedulingPriority = 99;
+          IOSchedulingClass = "realtime";
+          IOSchedulingPriority = 0;
+          UMask = "0002";
+        } // (if cfg.user != null then {
+          Group = cfg.group;
+          User = cfg.user;
+        } else {
+          DynamicUser = true;
+          User = "klipper";
+        });
+      };
+
+    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, serial }: if enable then pkgs.klipper-firmware.override {
+            mcu = lib.strings.sanitizeDerivationName mcu;
+            firmwareConfig = configFile;
+          } else null)
+          cfg.firmwares);
+        firmwareFlasher = mapAttrsToList
+          (mcu: firmware: pkgs.klipper-flash.override {
+            mcu = lib.strings.sanitizeDerivationName mcu;
+            klipper-firmware = firmware;
+            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/nixos/modules/services/misc/languagetool.nix b/nixos/modules/services/misc/languagetool.nix
new file mode 100644
index 000000000000..9adf792373b5
--- /dev/null
+++ b/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/nixos/modules/services/misc/leaps.nix b/nixos/modules/services/misc/leaps.nix
index f797218522c5..5522223ecc97 100644
--- a/nixos/modules/services/misc/leaps.nix
+++ b/nixos/modules/services/misc/leaps.nix
@@ -9,22 +9,22 @@ in
 {
   options = {
     services.leaps = {
-      enable = mkEnableOption "leaps";
+      enable = mkEnableOption (lib.mdDoc "leaps");
       port = mkOption {
         type = types.port;
         default = 8080;
-        description = "A port where leaps listens for incoming http requests";
+        description = lib.mdDoc "A port where leaps listens for incoming http requests";
       };
       address = mkOption {
         default = "";
         type = types.str;
         example = "127.0.0.1";
-        description = "Hostname or IP-address to listen to. By default it will listen on all interfaces.";
+        description = lib.mdDoc "Hostname or IP-address to listen to. By default it will listen on all interfaces.";
       };
       path = mkOption {
         default = "/";
         type = types.path;
-        description = "Subdirectory used for reverse proxy setups";
+        description = lib.mdDoc "Subdirectory used for reverse proxy setups";
       };
     };
   };
diff --git a/nixos/modules/services/misc/libreddit.nix b/nixos/modules/services/misc/libreddit.nix
index 77b34a856204..fd58928d2821 100644
--- a/nixos/modules/services/misc/libreddit.nix
+++ b/nixos/modules/services/misc/libreddit.nix
@@ -2,44 +2,44 @@
 
 with lib;
 
-  let
-    cfg = config.services.libreddit;
-
-    args = concatStringsSep " " ([
-      "--port ${toString cfg.port}"
-      "--address ${cfg.address}"
-    ] ++ optional cfg.redirect "--redirect-https");
+let
+  cfg = config.services.libreddit;
 
+  args = concatStringsSep " " ([
+    "--port ${toString cfg.port}"
+    "--address ${cfg.address}"
+  ]);
 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";
         example = "127.0.0.1";
         type =  types.str;
-        description = "The address to listen on";
+        description = lib.mdDoc "The address to listen on";
       };
 
       port = mkOption {
         default = 8080;
         example = 8000;
         type = types.port;
-        description = "The port to listen on";
-      };
-
-      redirect = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Enable the redirecting to HTTPS";
+        description = lib.mdDoc "The port to listen on";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the libreddit web interface";
+        description = lib.mdDoc "Open ports in the firewall for the libreddit web interface";
       };
 
     };
@@ -52,10 +52,35 @@ 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";
+          # Hardening
+          CapabilityBoundingSet = if (cfg.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ];
+          DeviceAllow = [ "" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          PrivateDevices = true;
+          # A private user cannot have process capabilities on the host's user
+          # namespace and thus CAP_NET_BIND_SERVICE has no effect.
+          PrivateUsers = (cfg.port >= 1024);
+          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;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+          UMask = "0077";
         };
     };
 
diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
index 20153c7e61a6..92b00054bdff 100644
--- a/nixos/modules/services/misc/lidarr.nix
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -8,25 +8,25 @@ in
 {
   options = {
     services.lidarr = {
-      enable = mkEnableOption "Lidarr";
+      enable = mkEnableOption (lib.mdDoc "Lidarr");
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/lidarr/.config/Lidarr";
-        description = "The directory where Lidarr stores its data files.";
+        description = lib.mdDoc "The directory where Lidarr stores its data files.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.lidarr;
         defaultText = literalExpression "pkgs.lidarr";
-        description = "The Lidarr package to use";
+        description = lib.mdDoc "The Lidarr package to use";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for Lidarr
         '';
       };
@@ -34,7 +34,7 @@ in
       user = mkOption {
         type = types.str;
         default = "lidarr";
-        description = ''
+        description = lib.mdDoc ''
           User account under which Lidarr runs.
         '';
       };
@@ -42,7 +42,7 @@ in
       group = mkOption {
         type = types.str;
         default = "lidarr";
-        description = ''
+        description = lib.mdDoc ''
           Group under which Lidarr runs.
         '';
       };
diff --git a/nixos/modules/services/misc/lifecycled.nix b/nixos/modules/services/misc/lifecycled.nix
index 1c8942998d6c..fb5cabb4f038 100644
--- a/nixos/modules/services/misc/lifecycled.nix
+++ b/nixos/modules/services/misc/lifecycled.nix
@@ -25,20 +25,20 @@ 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;
           default = "hourly";
-          description = ''
+          description = lib.mdDoc ''
             How often to trigger the queue cleaner.
 
             NOTE: This string should be a valid value for a systemd
-            timer's <literal>OnCalendar</literal> configuration. See
-            <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+            timer's `OnCalendar` configuration. See
+            {manpage}`systemd.timer(5)`
             for more information.
           '';
         };
@@ -46,7 +46,7 @@ in
         parallel = mkOption {
           type = types.ints.unsigned;
           default = 20;
-          description = ''
+          description = lib.mdDoc ''
             The number of parallel deletes to run.
           '';
         };
@@ -55,7 +55,7 @@ in
       instanceId = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The instance ID to listen for events for.
         '';
       };
@@ -63,7 +63,7 @@ in
       snsTopic = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The SNS topic that receives events.
         '';
       };
@@ -71,14 +71,14 @@ in
       noSpot = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Disable the spot termination listener.
         '';
       };
 
       handler = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The script to invoke to handle events.
         '';
       };
@@ -86,7 +86,7 @@ in
       json = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable JSON logging.
         '';
       };
@@ -94,7 +94,7 @@ in
       cloudwatchGroup = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Write logs to a specific Cloudwatch Logs group.
         '';
       };
@@ -102,7 +102,7 @@ in
       cloudwatchStream = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Write logs to a specific Cloudwatch Logs stream. Defaults to the instance ID.
         '';
       };
@@ -110,7 +110,7 @@ in
       debug = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable debugging information.
         '';
       };
@@ -120,7 +120,7 @@ in
       awsRegion = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The region used for accessing AWS services.
         '';
       };
diff --git a/nixos/modules/services/misc/logkeys.nix b/nixos/modules/services/misc/logkeys.nix
index 0082db63a06a..75d073a0c94b 100644
--- a/nixos/modules/services/misc/logkeys.nix
+++ b/nixos/modules/services/misc/logkeys.nix
@@ -6,10 +6,10 @@ let
   cfg = config.services.logkeys;
 in {
   options.services.logkeys = {
-    enable = mkEnableOption "logkeys service";
+    enable = mkEnableOption (lib.mdDoc "logkeys service");
 
     device = mkOption {
-      description = "Use the given device as keyboard input event device instead of /dev/input/eventX default.";
+      description = lib.mdDoc "Use the given device as keyboard input event device instead of /dev/input/eventX default.";
       default = null;
       type = types.nullOr types.str;
       example = "/dev/input/event15";
diff --git a/nixos/modules/services/misc/mame.nix b/nixos/modules/services/misc/mame.nix
index dd6c5ef9aa00..6e9d2fd26cff 100644
--- a/nixos/modules/services/misc/mame.nix
+++ b/nixos/modules/services/misc/mame.nix
@@ -12,19 +12,19 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to setup TUN/TAP Ethernet interface for MAME emulator.
         '';
       };
       user = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User from which you run MAME binary.
         '';
       };
       hostAddr = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           IP address of the host system. Usually an address of the main network
           adapter or the adapter through which you get an internet connection.
         '';
@@ -32,9 +32,9 @@ in
       };
       emuAddr = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           IP address of the guest system. The same you set inside guest OS under
-          MAME. Should be on the same subnet as <option>services.mame.hostAddr</option>.
+          MAME. Should be on the same subnet as {option}`services.mame.hostAddr`.
         '';
         example = "192.168.31.155";
       };
diff --git a/nixos/modules/services/misc/mbpfan.nix b/nixos/modules/services/misc/mbpfan.nix
index e0a4d8a13e75..d467aa879767 100644
--- a/nixos/modules/services/misc/mbpfan.nix
+++ b/nixos/modules/services/misc/mbpfan.nix
@@ -10,13 +10,13 @@ let
 
 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 = ''
+      description = lib.mdDoc ''
         The package used for the mbpfan daemon.
       '';
     };
@@ -24,52 +24,46 @@ in {
     verbose = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If true, sets the log level to verbose.
       '';
     };
 
     settings = mkOption {
       default = {};
-      description = "The INI configuration for Mbpfan.";
+      description = lib.mdDoc "INI configuration for Mbpfan.";
       type = types.submodule {
         freeformType = settingsFormat.type;
 
         options.general.min_fan1_speed = mkOption {
           type = types.nullOr types.int;
           default = 2000;
-          description = ''
-            The minimum fan speed. Setting to null enables automatic detection.
-            Check minimum fan limits with "cat /sys/devices/platform/applesmc.768/fan*_min".
-          '';
-        };
-        options.general.max_fan1_speed = mkOption {
-          type = types.nullOr types.int;
-          default = 6199;
-          description = ''
-            The maximum fan speed. Setting to null enables automatic detection.
-            Check maximum fan limits with "cat /sys/devices/platform/applesmc.768/fan*_max".
+          description = lib.mdDoc ''
+            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;
-          description = "Temperature below which fan speed will be at minimum. Try ranges 55-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;
-          description = "Fan will increase speed when higher than this temperature. Try ranges 58-66.";
+          description = lib.mdDoc "If temperature is above this, fan speed will gradually increase.";
         };
         options.general.max_temp = mkOption {
           type = types.int;
           default = 86;
-          description = "Fan will run at full speed above this temperature. Do not set it > 90.";
+          description = lib.mdDoc "If temperature is above this, fans will run at maximum speed.";
         };
         options.general.polling_interval = mkOption {
           type = types.int;
           default = 1;
-          description = "The polling interval.";
+          description = lib.mdDoc "The polling interval.";
         };
       };
     };
diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix
index ee5c0ef8d277..632b7caaac40 100644
--- a/nixos/modules/services/misc/mediatomb.nix
+++ b/nixos/modules/services/misc/mediatomb.nix
@@ -15,19 +15,19 @@ let
     options = {
       path = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Absolute directory path to the media directory to index.
         '';
       };
       recursive = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether the indexation must take place recursively or not.";
+        description = lib.mdDoc "Whether the indexation must take place recursively or not.";
       };
       hidden-files = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to index the hidden files or not.";
+        description = lib.mdDoc "Whether to index the hidden files or not.";
       };
     };
   };
@@ -202,7 +202,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Gerbera/Mediatomb DLNA server.
         '';
       };
@@ -210,7 +210,7 @@ in {
       serverName = mkOption {
         type = types.str;
         default = "Gerbera (Mediatomb)";
-        description = ''
+        description = lib.mdDoc ''
           How to identify the server on the network.
         '';
       };
@@ -219,7 +219,7 @@ in {
         type = types.package;
         default = pkgs.gerbera;
         defaultText = literalExpression "pkgs.gerbera";
-        description = ''
+        description = lib.mdDoc ''
           Underlying package to be used with the module.
         '';
       };
@@ -227,7 +227,7 @@ in {
       ps3Support = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable ps3 specific tweaks.
           WARNING: incompatible with DSM 320 support.
         '';
@@ -236,7 +236,7 @@ in {
       dsmSupport = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable D-Link DSM 320 specific tweaks.
           WARNING: incompatible with ps3 support.
         '';
@@ -245,7 +245,7 @@ in {
       tg100Support = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Telegent TG100 specific tweaks.
         '';
       };
@@ -253,7 +253,7 @@ in {
       transcoding = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable transcoding.
         '';
       };
@@ -262,7 +262,7 @@ in {
         type = types.path;
         default = "/var/lib/${name}";
         defaultText = literalExpression ''"/var/lib/''${config.${opt.package}.pname}"'';
-        description = ''
+        description = lib.mdDoc ''
           The directory where Gerbera/Mediatomb stores its state, data, etc.
         '';
       };
@@ -270,7 +270,7 @@ in {
       pcDirectoryHide = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to list the top-level directory or not (from upnp client standpoint).
         '';
       };
@@ -278,19 +278,19 @@ in {
       user = mkOption {
         type = types.str;
         default = "mediatomb";
-        description = "User account under which the service runs.";
+        description = lib.mdDoc "User account under which the service runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "mediatomb";
-        description = "Group account under which the service runs.";
+        description = lib.mdDoc "Group account under which the service runs.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 49152;
-        description = ''
+        description = lib.mdDoc ''
           The network port to listen on.
         '';
       };
@@ -298,7 +298,7 @@ in {
       interface = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           A specific interface to bind to.
         '';
       };
@@ -306,12 +306,12 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If false (the default), this is up to the user to declare the firewall rules.
           If true, this opens port 1900 (tcp and udp) and the port specified by
-          <option>sercvices.mediatomb.port</option>.
+          {option}`sercvices.mediatomb.port`.
 
-          If the option <option>services.mediatomb.interface</option> is set,
+          If the option {option}`services.mediatomb.interface` is set,
           the firewall rules opened are dedicated to that interface. Otherwise,
           those rules are opened globally.
         '';
@@ -320,7 +320,7 @@ in {
       uuid = mkOption {
         type = types.str;
         default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687";
-        description = ''
+        description = lib.mdDoc ''
           A unique (on your network) to identify the server by.
         '';
       };
@@ -328,7 +328,7 @@ in {
       mediaDirectories = mkOption {
         type = with types; listOf (submodule mediaDirectory);
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Declare media directories to index.
         '';
         example = [
@@ -340,12 +340,12 @@ in {
       customCfg = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Allow the service to create and use its own config file inside the <literal>dataDir</literal> as
-          configured by <option>services.mediatomb.dataDir</option>.
+        description = lib.mdDoc ''
+          Allow the service to create and use its own config file inside the `dataDir` as
+          configured by {option}`services.mediatomb.dataDir`.
           Deactivated by default, the service then runs with the configuration generated from this module.
           Otherwise, when enabled, no service configuration is generated. Gerbera/Mediatomb then starts using
-          config.xml within the configured <literal>dataDir</literal>. It's up to the user to make a correct
+          config.xml within the configured `dataDir`. It's up to the user to make a correct
           configuration file.
         '';
       };
@@ -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/nixos/modules/services/misc/metabase.nix b/nixos/modules/services/misc/metabase.nix
index e78100a046a2..883fa0b95911 100644
--- a/nixos/modules/services/misc/metabase.nix
+++ b/nixos/modules/services/misc/metabase.nix
@@ -13,13 +13,13 @@ in {
   options = {
 
     services.metabase = {
-      enable = mkEnableOption "Metabase service";
+      enable = mkEnableOption (lib.mdDoc "Metabase service");
 
       listen = {
         ip = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = ''
+          description = lib.mdDoc ''
             IP address that Metabase should listen on.
           '';
         };
@@ -27,7 +27,7 @@ in {
         port = mkOption {
           type = types.port;
           default = 3000;
-          description = ''
+          description = lib.mdDoc ''
             Listen port for Metabase.
           '';
         };
@@ -37,7 +37,7 @@ in {
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable SSL (https) support.
           '';
         };
@@ -45,7 +45,7 @@ in {
         port = mkOption {
           type = types.port;
           default = 8443;
-          description = ''
+          description = lib.mdDoc ''
             Listen port over SSL (https) for Metabase.
           '';
         };
@@ -54,8 +54,8 @@ in {
           type = types.nullOr types.path;
           default = "${dataDir}/metabase.jks";
           example = "/etc/secrets/keystore.jks";
-          description = ''
-            <link xlink:href="https://www.digitalocean.com/community/tutorials/java-keytool-essentials-working-with-java-keystores">Java KeyStore</link> file containing the certificates.
+          description = lib.mdDoc ''
+            [Java KeyStore](https://www.digitalocean.com/community/tutorials/java-keytool-essentials-working-with-java-keystores) file containing the certificates.
           '';
         };
 
@@ -64,7 +64,7 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for Metabase.
         '';
       };
diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix
index b75227effa04..62064b5d90fb 100644
--- a/nixos/modules/services/misc/moonraker.nix
+++ b/nixos/modules/services/misc/moonraker.nix
@@ -14,26 +14,26 @@ let
 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;
         default = config.services.klipper.apiSocket;
         defaultText = literalExpression "config.services.klipper.apiSocket";
-        description = "Path to Klipper's API socket.";
+        description = lib.mdDoc "Path to Klipper's API socket.";
       };
 
       stateDir = mkOption {
         type = types.path;
         default = "/var/lib/moonraker";
-        description = "The directory containing the Moonraker databases.";
+        description = lib.mdDoc "The directory containing the Moonraker databases.";
       };
 
       configDir = mkOption {
         type = types.path;
         default = cfg.stateDir + "/config";
         defaultText = literalExpression ''config.${opt.stateDir} + "/config"'';
-        description = ''
+        description = lib.mdDoc ''
           The directory containing client-writable configuration files.
 
           Clients will be able to edit files in this directory via the API. This directory must be writable.
@@ -43,26 +43,26 @@ in {
       user = mkOption {
         type = types.str;
         default = "moonraker";
-        description = "User account under which Moonraker runs.";
+        description = lib.mdDoc "User account under which Moonraker runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "moonraker";
-        description = "Group account under which Moonraker runs.";
+        description = lib.mdDoc "Group account under which Moonraker runs.";
       };
 
       address = mkOption {
         type = types.str;
         default = "127.0.0.1";
         example = "0.0.0.0";
-        description = "The IP or host to listen on.";
+        description = lib.mdDoc "The IP or host to listen on.";
       };
 
       port = mkOption {
         type = types.ints.unsigned;
         default = 7125;
-        description = "The port to listen on.";
+        description = lib.mdDoc "The port to listen on.";
       };
 
       settings = mkOption {
@@ -74,8 +74,8 @@ in {
             cors_domains = [ "https://app.fluidd.xyz" ];
           };
         };
-        description = ''
-          Configuration for Moonraker. See the <link xlink:href="https://moonraker.readthedocs.io/en/latest/configuration/">documentation</link>
+        description = lib.mdDoc ''
+          Configuration for Moonraker. See the [documentation](https://moonraker.readthedocs.io/en/latest/configuration/)
           for supported values.
         '';
       };
@@ -83,12 +83,12 @@ in {
       allowSystemControl = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow Moonraker to perform system-level operations.
 
           Moonraker exposes APIs to perform system-level operations, such as
           reboot, shutdown, and management of systemd units. See the
-          <link xlink:href="https://moonraker.readthedocs.io/en/latest/web_api/#machine-commands">documentation</link>
+          [documentation](https://moonraker.readthedocs.io/en/latest/web_api/#machine-commands)
           for details on what clients are able to do.
         '';
       };
@@ -123,7 +123,11 @@ in {
           host = cfg.address;
           port = cfg.port;
           klippy_uds_address = cfg.klipperSocket;
+        };
+        file_manager = {
           config_path = cfg.configDir;
+        };
+        database = {
           database_path = "${cfg.stateDir}/database";
         };
       };
@@ -153,6 +157,7 @@ in {
 
       serviceConfig = {
         WorkingDirectory = cfg.stateDir;
+        PrivateTmp = true;
         Group = cfg.group;
         User = cfg.user;
       };
@@ -175,4 +180,9 @@ in {
       });
     '';
   };
+
+  meta.maintainers = with maintainers; [
+    cab404
+    vtuan10
+  ];
 }
diff --git a/nixos/modules/services/misc/mx-puppet-discord.nix b/nixos/modules/services/misc/mx-puppet-discord.nix
index 6214f7f7eb6b..36c9f8b122ea 100644
--- a/nixos/modules/services/misc/mx-puppet-discord.nix
+++ b/nixos/modules/services/misc/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;
@@ -57,11 +57,11 @@ in {
             relay.whitelist = [ "@.*:example.com" ];
           }
         '';
-        description = ''
-          <filename>config.yaml</filename> configuration as a Nix attribute set.
+        description = lib.mdDoc ''
+          {file}`config.yaml` configuration as a Nix attribute set.
           Configuration options should match those described in
-          <link xlink:href="https://github.com/matrix-discord/mx-puppet-discord/blob/master/sample.config.yaml">
-          sample.config.yaml</link>.
+          [
+          sample.config.yaml](https://github.com/matrix-discord/mx-puppet-discord/blob/master/sample.config.yaml).
         '';
       };
       serviceDependencies = mkOption {
@@ -70,7 +70,7 @@ in {
         defaultText = literalExpression ''
           optional config.services.matrix-synapse.enable "matrix-synapse.service"
         '';
-        description = ''
+        description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service.
         '';
       };
@@ -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/nixos/modules/services/misc/n8n.nix b/nixos/modules/services/misc/n8n.nix
index 77e717eeff9f..f59df471e1e0 100644
--- a/nixos/modules/services/misc/n8n.nix
+++ b/nixos/modules/services/misc/n8n.nix
@@ -10,19 +10,19 @@ in
 {
   options.services.n8n = {
 
-    enable = mkEnableOption "n8n server";
+    enable = mkEnableOption (lib.mdDoc "n8n server");
 
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = "Open ports in the firewall for the n8n web interface.";
+      description = lib.mdDoc "Open ports in the firewall for the n8n web interface.";
     };
 
     settings = mkOption {
       type = format.type;
       default = {};
-      description = ''
-        Configuration for n8n, see <link xlink:href="https://docs.n8n.io/reference/configuration.html"/>
+      description = lib.mdDoc ''
+        Configuration for n8n, see <https://docs.n8n.io/reference/configuration.html>
         for supported values.
       '';
     };
diff --git a/nixos/modules/services/misc/nitter.nix b/nixos/modules/services/misc/nitter.nix
index 97005c9d914f..f0cb5cc15138 100644
--- a/nixos/modules/services/misc/nitter.nix
+++ b/nixos/modules/services/misc/nitter.nix
@@ -47,13 +47,13 @@ in
 {
   options = {
     services.nitter = {
-      enable = mkEnableOption "If enabled, start Nitter.";
+      enable = mkEnableOption (lib.mdDoc "Nitter");
 
       package = mkOption {
         default = pkgs.nitter;
         type = types.package;
         defaultText = literalExpression "pkgs.nitter";
-        description = "The nitter derivation to use.";
+        description = lib.mdDoc "The nitter derivation to use.";
       };
 
       server = {
@@ -61,46 +61,46 @@ in
           type =  types.str;
           default = "0.0.0.0";
           example = "127.0.0.1";
-          description = "The address to listen on.";
+          description = lib.mdDoc "The address to listen on.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8080;
           example = 8000;
-          description = "The port to listen on.";
+          description = lib.mdDoc "The port to listen on.";
         };
 
         https = mkOption {
           type = types.bool;
           default = false;
-          description = "Set secure attribute on cookies. Keep it disabled to enable cookies when not using HTTPS.";
+          description = lib.mdDoc "Set secure attribute on cookies. Keep it disabled to enable cookies when not using HTTPS.";
         };
 
         httpMaxConnections = mkOption {
           type = types.int;
           default = 100;
-          description = "Maximum number of HTTP connections.";
+          description = lib.mdDoc "Maximum number of HTTP connections.";
         };
 
         staticDir = mkOption {
           type = types.path;
           default = "${cfg.package}/share/nitter/public";
           defaultText = literalExpression ''"''${config.services.nitter.package}/share/nitter/public"'';
-          description = "Path to the static files directory.";
+          description = lib.mdDoc "Path to the static files directory.";
         };
 
         title = mkOption {
           type = types.str;
           default = "nitter";
-          description = "Title of the instance.";
+          description = lib.mdDoc "Title of the instance.";
         };
 
         hostname = mkOption {
           type = types.str;
           default = "localhost";
           example = "nitter.net";
-          description = "Hostname of the instance.";
+          description = lib.mdDoc "Hostname of the instance.";
         };
       };
 
@@ -108,37 +108,37 @@ in
         listMinutes = mkOption {
           type = types.int;
           default = 240;
-          description = "How long to cache list info (not the tweets, so keep it high).";
+          description = lib.mdDoc "How long to cache list info (not the tweets, so keep it high).";
         };
 
         rssMinutes = mkOption {
           type = types.int;
           default = 10;
-          description = "How long to cache RSS queries.";
+          description = lib.mdDoc "How long to cache RSS queries.";
         };
 
         redisHost = mkOption {
           type = types.str;
           default = "localhost";
-          description = "Redis host.";
+          description = lib.mdDoc "Redis host.";
         };
 
         redisPort = mkOption {
           type = types.port;
           default = 6379;
-          description = "Redis port.";
+          description = lib.mdDoc "Redis port.";
         };
 
         redisConnections = mkOption {
           type = types.int;
           default = 20;
-          description = "Redis connection pool size.";
+          description = lib.mdDoc "Redis connection pool size.";
         };
 
         redisMaxConnections = mkOption {
           type = types.int;
           default = 30;
-          description = ''
+          description = lib.mdDoc ''
             Maximum number of connections to Redis.
 
             New connections are opened when none are available, but if the
@@ -152,13 +152,13 @@ in
         base64Media = mkOption {
           type = types.bool;
           default = false;
-          description = "Use base64 encoding for proxied media URLs.";
+          description = lib.mdDoc "Use base64 encoding for proxied media URLs.";
         };
 
         tokenCount = mkOption {
           type = types.int;
           default = 10;
-          description = ''
+          description = lib.mdDoc ''
             Minimum amount of usable tokens.
 
             Tokens are used to authorize API requests, but they expire after
@@ -175,122 +175,122 @@ in
           type = types.str;
           default = "";
           example = "nitter.net";
-          description = "Replace Twitter links with links to this instance (blank to disable).";
+          description = lib.mdDoc "Replace Twitter links with links to this instance (blank to disable).";
         };
 
         replaceYouTube = mkOption {
           type = types.str;
           default = "";
           example = "piped.kavin.rocks";
-          description = "Replace YouTube links with links to this instance (blank to disable).";
+          description = lib.mdDoc "Replace YouTube links with links to this instance (blank to disable).";
         };
 
         replaceInstagram = mkOption {
           type = types.str;
           default = "";
-          description = "Replace Instagram links with links to this instance (blank to disable).";
+          description = lib.mdDoc "Replace Instagram links with links to this instance (blank to disable).";
         };
 
         mp4Playback = mkOption {
           type = types.bool;
           default = true;
-          description = "Enable MP4 video playback.";
+          description = lib.mdDoc "Enable MP4 video playback.";
         };
 
         hlsPlayback = mkOption {
           type = types.bool;
           default = false;
-          description = "Enable HLS video streaming (requires JavaScript).";
+          description = lib.mdDoc "Enable HLS video streaming (requires JavaScript).";
         };
 
         proxyVideos = mkOption {
           type = types.bool;
           default = true;
-          description = "Proxy video streaming through the server (might be slow).";
+          description = lib.mdDoc "Proxy video streaming through the server (might be slow).";
         };
 
         muteVideos = mkOption {
           type = types.bool;
           default = false;
-          description = "Mute videos by default.";
+          description = lib.mdDoc "Mute videos by default.";
         };
 
         autoplayGifs = mkOption {
           type = types.bool;
           default = true;
-          description = "Autoplay GIFs.";
+          description = lib.mdDoc "Autoplay GIFs.";
         };
 
         theme = mkOption {
           type = types.str;
           default = "Nitter";
-          description = "Instance theme.";
+          description = lib.mdDoc "Instance theme.";
         };
 
         infiniteScroll = mkOption {
           type = types.bool;
           default = false;
-          description = "Infinite scrolling (requires JavaScript, experimental!).";
+          description = lib.mdDoc "Infinite scrolling (requires JavaScript, experimental!).";
         };
 
         stickyProfile = mkOption {
           type = types.bool;
           default = true;
-          description = "Make profile sidebar stick to top.";
+          description = lib.mdDoc "Make profile sidebar stick to top.";
         };
 
         bidiSupport = mkOption {
           type = types.bool;
           default = false;
-          description = "Support bidirectional text (makes clicking on tweets harder).";
+          description = lib.mdDoc "Support bidirectional text (makes clicking on tweets harder).";
         };
 
         hideTweetStats = mkOption {
           type = types.bool;
           default = false;
-          description = "Hide tweet stats (replies, retweets, likes).";
+          description = lib.mdDoc "Hide tweet stats (replies, retweets, likes).";
         };
 
         hideBanner = mkOption {
           type = types.bool;
           default = false;
-          description = "Hide profile banner.";
+          description = lib.mdDoc "Hide profile banner.";
         };
 
         hidePins = mkOption {
           type = types.bool;
           default = false;
-          description = "Hide pinned tweets.";
+          description = lib.mdDoc "Hide pinned tweets.";
         };
 
         hideReplies = mkOption {
           type = types.bool;
           default = false;
-          description = "Hide tweet replies.";
+          description = lib.mdDoc "Hide tweet replies.";
         };
       };
 
       settings = mkOption {
         type = types.attrs;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Add settings here to override NixOS module generated settings.
 
           Check the official repository for the available settings:
-          https://github.com/zedeus/nitter/blob/master/nitter.conf
+          https://github.com/zedeus/nitter/blob/master/nitter.example.conf
         '';
       };
 
       redisCreateLocally = mkOption {
         type = types.bool;
         default = true;
-        description = "Configure local Redis server for Nitter.";
+        description = lib.mdDoc "Configure local Redis server for Nitter.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for Nitter web interface.";
+        description = lib.mdDoc "Open ports in the firewall for Nitter web interface.";
       };
     };
   };
@@ -347,8 +347,9 @@ in
         };
     };
 
-    services.redis = lib.mkIf (cfg.redisCreateLocally) {
+    services.redis.servers.nitter = lib.mkIf (cfg.redisCreateLocally) {
       enable = true;
+      port = cfg.cache.redisPort;
     };
 
     networking.firewall = mkIf cfg.openFirewall {
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index a4d2d10af70f..f9f0736efcb9 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -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;
 
@@ -127,7 +128,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Nix.
           Disabling Nix makes the system hard to modify and the Nix programs and configuration will not be made available by NixOS itself.
         '';
@@ -137,7 +138,7 @@ in
         type = types.package;
         default = pkgs.nix;
         defaultText = literalExpression "pkgs.nix";
-        description = ''
+        description = lib.mdDoc ''
           This option specifies the Nix package instance to use throughout the system.
         '';
       };
@@ -145,9 +146,9 @@ in
       distributedBuilds = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to distribute builds to the machines listed in
-          <option>nix.buildMachines</option>.
+          {option}`nix.buildMachines`.
         '';
       };
 
@@ -155,30 +156,29 @@ in
         type = types.enum [ "other" "batch" "idle" ];
         default = "other";
         example = "batch";
-        description = ''
+        description = lib.mdDoc ''
           Nix daemon process CPU scheduling policy. This policy propagates to
-          build processes. <literal>other</literal> is the default scheduling
-          policy for regular tasks. The <literal>batch</literal> policy is
-          similar to <literal>other</literal>, but optimised for
-          non-interactive tasks. <literal>idle</literal> is for extremely
+          build processes. `other` is the default scheduling
+          policy for regular tasks. The `batch` policy is
+          similar to `other`, but optimised for
+          non-interactive tasks. `idle` is for extremely
           low-priority tasks that should only be run when no other task
           requires CPU time.
 
-          Please note that while using the <literal>idle</literal> policy may
+          Please note that while using the `idle` policy may
           greatly improve responsiveness of a system performing expensive
           builds, it may also slow down and potentially starve crucial
           configuration updates during load.
 
-          <literal>idle</literal> may therefore be a sensible policy for
+          `idle` may therefore be a sensible policy for
           systems that experience only intermittent phases of high CPU load,
           such as desktop or portable computers used interactively. Other
-          systems should use the <literal>other</literal> or
-          <literal>batch</literal> policy instead.
+          systems should use the `other` or
+          `batch` policy instead.
 
           For more fine-grained resource control, please refer to
-          <citerefentry><refentrytitle>systemd.resource-control
-          </refentrytitle><manvolnum>5</manvolnum></citerefentry> and adjust
-          <option>systemd.services.nix-daemon</option> directly.
+          {manpage}`systemd.resource-control(5)` and adjust
+          {option}`systemd.services.nix-daemon` directly.
       '';
       };
 
@@ -186,30 +186,30 @@ in
         type = types.enum [ "best-effort" "idle" ];
         default = "best-effort";
         example = "idle";
-        description = ''
+        description = lib.mdDoc ''
           Nix daemon process I/O scheduling class. This class propagates to
-          build processes. <literal>best-effort</literal> is the default
-          class for regular tasks. The <literal>idle</literal> class is for
+          build processes. `best-effort` is the default
+          class for regular tasks. The `idle` class is for
           extremely low-priority tasks that should only perform I/O when no
           other task does.
 
-          Please note that while using the <literal>idle</literal> scheduling
+          Please note that while using the `idle` scheduling
           class can improve responsiveness of a system performing expensive
           builds, it might also slow down or starve crucial configuration
           updates during load.
 
-          <literal>idle</literal> may therefore be a sensible class for
+          `idle` may therefore be a sensible class for
           systems that experience only intermittent phases of high I/O load,
           such as desktop or portable computers used interactively. Other
-          systems should use the <literal>best-effort</literal> class.
+          systems should use the `best-effort` class.
       '';
       };
 
       daemonIOSchedPriority = mkOption {
         type = types.int;
-        default = 0;
+        default = 4;
         example = 1;
-        description = ''
+        description = lib.mdDoc ''
           Nix daemon process I/O scheduling priority. This priority propagates
           to build processes. The supported priorities depend on the
           scheduling policy: With idle, priorities are not used in scheduling
@@ -224,18 +224,31 @@ in
             hostName = mkOption {
               type = types.str;
               example = "nixbuilder.example.org";
-              description = ''
+              description = lib.mdDoc ''
                 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.
               '';
             };
@@ -243,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.
               '';
             };
@@ -254,18 +267,18 @@ in
               type = types.nullOr types.str;
               default = null;
               example = "builder";
-              description = ''
+              description = lib.mdDoc ''
                 The username to log in as on the remote host. This user must be
                 able to log in and run nix commands non-interactively. It must
                 also be privileged to build derivations, so must be included in
-                <option>nix.settings.trusted-users</option>.
+                {option}`nix.settings.trusted-users`.
               '';
             };
             sshKey = mkOption {
               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
@@ -278,7 +291,7 @@ in
             maxJobs = mkOption {
               type = types.int;
               default = 1;
-              description = ''
+              description = lib.mdDoc ''
                 The number of concurrent jobs the build machine supports. The
                 build machine will enforce its own limits, but this allows hydra
                 to schedule better since there is no work-stealing between build
@@ -288,7 +301,7 @@ in
             speedFactor = mkOption {
               type = types.int;
               default = 1;
-              description = ''
+              description = lib.mdDoc ''
                 The relative speed of this builder. This is an arbitrary integer
                 that indicates the speed of this builder, relative to other
                 builders. Higher is faster.
@@ -298,18 +311,18 @@ 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 {
               type = types.listOf types.str;
               default = [ ];
               example = [ "kvm" "big-parallel" ];
-              description = ''
+              description = lib.mdDoc ''
                 A list of features supported by this builder. The builder will
                 be ignored for derivations that require features not in this
                 list.
@@ -318,18 +331,18 @@ in
             publicHostKey = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The (base64-encoded) public host key of this builder. The field
-                is calculated via <command>base64 -w0 /etc/ssh/ssh_host_type_key.pub</command>.
+                is calculated via {command}`base64 -w0 /etc/ssh/ssh_host_type_key.pub`.
                 If null, SSH will use its regular known-hosts file when connecting.
               '';
             };
           };
         });
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           This option lists the machines to be used if distributed builds are
-          enabled (see <option>nix.distributedBuilds</option>).
+          enabled (see {option}`nix.distributedBuilds`).
           Nix will perform derivations on those machines via SSH by copying the
           inputs to the Nix store on the remote machine, starting the build,
           then copying the output back to the local Nix store.
@@ -341,30 +354,19 @@ in
         type = types.attrs;
         internal = true;
         default = { };
-        description = "Environment variables used by Nix.";
+        description = lib.mdDoc "Environment variables used by Nix.";
       };
 
       nrBuildUsers = mkOption {
         type = types.int;
-        description = ''
-          Number of <literal>nixbld</literal> user accounts created to
+        description = lib.mdDoc ''
+          Number of `nixbld` user accounts created to
           perform secure concurrent builds.  If you receive an error
           message saying that “all build users are currently in use”,
           you should increase this value.
         '';
       };
 
-      readOnlyStore = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          If set, NixOS will enforce the immutability of the Nix store
-          by making <filename>/nix/store</filename> a read-only bind
-          mount.  Nix will automatically make the store writable when
-          needed.
-        '';
-      };
-
       nixPath = mkOption {
         type = types.listOf types.str;
         default = [
@@ -372,19 +374,26 @@ in
           "nixos-config=/etc/nixos/configuration.nix"
           "/nix/var/nix/profiles/per-user/root/channels"
         ];
-        description = ''
+        description = lib.mdDoc ''
           The default Nix expression search path, used by the Nix
           evaluator to look up paths enclosed in angle brackets
-          (e.g. <literal>&lt;nixpkgs&gt;</literal>).
+          (e.g. `<nixpkgs>`).
         '';
       };
 
       checkConfig = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          If enabled (the default), checks for data type mismatches and that Nix
-          can parse the generated nix.conf.
+        description = lib.mdDoc ''
+          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.
         '';
       };
 
@@ -395,6 +404,7 @@ in
               str
               int
               bool
+              path
               package
             ]);
           in
@@ -404,45 +414,46 @@ in
               from = mkOption {
                 type = referenceAttrs;
                 example = { type = "indirect"; id = "nixpkgs"; };
-                description = "The flake reference to be rewritten.";
+                description = lib.mdDoc "The flake reference to be rewritten.";
               };
               to = mkOption {
                 type = referenceAttrs;
                 example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; };
-                description = "The flake reference <option>from</option> is rewritten to.";
+                description = lib.mdDoc "The flake reference {option}`from` is rewritten to.";
               };
               flake = mkOption {
                 type = types.nullOr types.attrs;
                 default = null;
                 example = literalExpression "nixpkgs";
-                description = ''
-                  The flake input <option>from</option> is rewritten to.
+                description = lib.mdDoc ''
+                  The flake input {option}`from` is rewritten to.
                 '';
               };
               exact = mkOption {
                 type = types.bool;
                 default = true;
-                description = ''
-                  Whether the <option>from</option> reference needs to match exactly. If set,
-                  a <option>from</option> reference like <literal>nixpkgs</literal> does not
-                  match with a reference like <literal>nixpkgs/nixos-20.03</literal>.
+                description = lib.mdDoc ''
+                  Whether the {option}`from` reference needs to match exactly. If set,
+                  a {option}`from` reference like `nixpkgs` does not
+                  match with a reference like `nixpkgs/nixos-20.03`.
                 '';
               };
             };
             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
+              ));
             };
           }
         ));
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           A system-wide flake registry.
         '';
       };
@@ -454,7 +465,7 @@ in
           keep-outputs = true
           keep-derivations = true
         '';
-        description = "Additional text appended to <filename>nix.conf</filename>.";
+        description = lib.mdDoc "Additional text appended to {file}`nix.conf`.";
       };
 
       settings = mkOption {
@@ -466,7 +477,7 @@ in
               type = types.either types.int (types.enum [ "auto" ]);
               default = "auto";
               example = 64;
-              description = ''
+              description = lib.mdDoc ''
                 This option defines the maximum number of jobs that Nix will try to
                 build in parallel. The default is auto, which means it will use all
                 available logical cores. It is recommend to set it to the total
@@ -479,7 +490,7 @@ in
               type = types.bool;
               default = false;
               example = true;
-              description = ''
+              description = lib.mdDoc ''
                 If set to true, Nix automatically detects files in the store that have
                 identical contents, and replaces them with hard links to a single copy.
                 This saves disk space. If set to false (the default), you can still run
@@ -491,7 +502,7 @@ in
               type = types.int;
               default = 0;
               example = 64;
-              description = ''
+              description = lib.mdDoc ''
                 This option defines the maximum number of concurrent tasks during
                 one build. It affects, e.g., -j option for make.
                 The special value 0 means that the builder should use all
@@ -504,7 +515,7 @@ in
             sandbox = mkOption {
               type = types.either types.bool (types.enum [ "relaxed" ]);
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 If set, Nix will perform builds in a sandboxed environment that it
                 will set up automatically for each build. This prevents impurities
                 in builds by disallowing access to dependencies outside of the Nix
@@ -520,7 +531,7 @@ in
               type = types.listOf types.str;
               default = [ ];
               example = [ "/dev" "/proc" ];
-              description = ''
+              description = lib.mdDoc ''
                 Directories from the host filesystem to be included
                 in the sandbox.
               '';
@@ -528,7 +539,7 @@ in
 
             substituters = mkOption {
               type = types.listOf types.str;
-              description = ''
+              description = lib.mdDoc ''
                 List of binary cache URLs used to obtain pre-built binaries
                 of Nix packages.
 
@@ -540,21 +551,21 @@ in
               type = types.listOf types.str;
               default = [ ];
               example = [ "https://hydra.nixos.org/" ];
-              description = ''
+              description = lib.mdDoc ''
                 List of binary cache URLs that non-root users can use (in
                 addition to those specified using
-                <option>nix.settings.substituters</option>) by passing
-                <literal>--option binary-caches</literal> to Nix commands.
+                {option}`nix.settings.substituters`) by passing
+                `--option binary-caches` to Nix commands.
               '';
             };
 
             require-sigs = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 If enabled (the default), Nix will only download binaries from binary caches if
                 they are cryptographically signed with any of the keys listed in
-                <option>nix.settings.trusted-public-keys</option>. If disabled, signatures are neither
+                {option}`nix.settings.trusted-public-keys`. If disabled, signatures are neither
                 required nor checked, so it's strongly recommended that you use only
                 trustworthy caches and https to prevent man-in-the-middle attacks.
               '';
@@ -563,13 +574,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.
               '';
             };
 
@@ -577,13 +588,13 @@ in
               type = types.listOf types.str;
               default = [ "root" ];
               example = [ "root" "alice" "@wheel" ];
-              description = ''
+              description = lib.mdDoc ''
                 A list of names of users that have additional rights when
                 connecting to the Nix daemon, such as the ability to specify
                 additional binary caches, or to import unsigned NARs. You
                 can also specify groups by prefixing them with
-                <literal>@</literal>; for instance,
-                <literal>@wheel</literal> means all users in the wheel
+                `@`; for instance,
+                `@wheel` means all users in the wheel
                 group.
               '';
             };
@@ -591,14 +602,14 @@ in
             system-features = mkOption {
               type = types.listOf types.str;
               example = [ "kvm" "big-parallel" "gccarch-skylake" ];
-              description = ''
+              description = lib.mdDoc ''
                 The set of features supported by the machine. Derivations
                 can express dependencies on system features through the
-                <literal>requiredSystemFeatures</literal> attribute.
+                `requiredSystemFeatures` attribute.
 
-                By default, pseudo-features <literal>nixos-test</literal>, <literal>benchmark</literal>,
-                and <literal>big-parallel</literal> used in Nixpkgs are set, <literal>kvm</literal>
-                is also included in it is avaliable.
+                By default, pseudo-features `nixos-test`, `benchmark`,
+                and `big-parallel` used in Nixpkgs are set, `kvm`
+                is also included in it is available.
               '';
             };
 
@@ -606,13 +617,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.
               '';
             };
@@ -628,22 +639,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.
-          </para>
-          <para>
-          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.
-          </para>
-          <para>
-          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.
         '';
       };
@@ -675,13 +681,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"
@@ -734,7 +742,7 @@ in
             CPUSchedulingPolicy = cfg.daemonCPUSchedPolicy;
             IOSchedulingClass = cfg.daemonIOSchedClass;
             IOSchedulingPriority = cfg.daemonIOSchedPriority;
-            LimitNOFILE = 4096;
+            LimitNOFILE = 1048576;
           };
 
         restartTriggers = [ nixConf ];
diff --git a/nixos/modules/services/misc/nix-gc.nix b/nixos/modules/services/misc/nix-gc.nix
index 0fcb01601017..97596d28cd89 100644
--- a/nixos/modules/services/misc/nix-gc.nix
+++ b/nixos/modules/services/misc/nix-gc.nix
@@ -17,20 +17,19 @@ in
       automatic = mkOption {
         default = false;
         type = types.bool;
-        description = "Automatically run the garbage collector at a specific time.";
+        description = lib.mdDoc "Automatically run the garbage collector at a specific time.";
       };
 
       dates = mkOption {
         type = types.str;
         default = "03:15";
         example = "weekly";
-        description = ''
+        description = lib.mdDoc ''
           How often or when garbage collection is performed. For most desktop and server systems
           a sufficient garbage collection is once a week.
 
           The format is described in
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
 
@@ -38,12 +37,11 @@ in
         default = "0";
         type = types.str;
         example = "45min";
-        description = ''
+        description = lib.mdDoc ''
           Add a randomized delay before each garbage collection.
           The delay will be chosen between zero and this value.
           This value must be a time span in the format specified by
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>
+          {manpage}`systemd.time(7)`
         '';
       };
 
@@ -51,7 +49,7 @@ in
         default = true;
         type = types.bool;
         example = false;
-        description = ''
+        description = lib.mdDoc ''
           Takes a boolean argument. If true, the time when the service
           unit was last triggered is stored on disk. When the timer is
           activated, the service unit is triggered immediately if it
@@ -67,8 +65,8 @@ in
         default = "";
         example = "--max-freed $((64 * 1024**3))";
         type = types.str;
-        description = ''
-          Options given to <filename>nix-collect-garbage</filename> when the
+        description = lib.mdDoc ''
+          Options given to {file}`nix-collect-garbage` when the
           garbage collector is run automatically.
         '';
       };
diff --git a/nixos/modules/services/misc/nix-optimise.nix b/nixos/modules/services/misc/nix-optimise.nix
index acf8177b146a..db8148c060e7 100644
--- a/nixos/modules/services/misc/nix-optimise.nix
+++ b/nixos/modules/services/misc/nix-optimise.nix
@@ -17,16 +17,15 @@ in
       automatic = mkOption {
         default = false;
         type = types.bool;
-        description = "Automatically run the nix store optimiser at a specific time.";
+        description = lib.mdDoc "Automatically run the nix store optimiser at a specific time.";
       };
 
       dates = mkOption {
         default = ["03:45"];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specification (in the format described by
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>) of the time at
+          {manpage}`systemd.time(7)`) of the time at
           which the optimiser will run.
         '';
       };
diff --git a/nixos/modules/services/misc/nix-ssh-serve.nix b/nixos/modules/services/misc/nix-ssh-serve.nix
index 355fad5db468..b656692ca01c 100644
--- a/nixos/modules/services/misc/nix-ssh-serve.nix
+++ b/nixos/modules/services/misc/nix-ssh-serve.nix
@@ -14,26 +14,26 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable serving the Nix store as a remote store via SSH.";
+        description = lib.mdDoc "Whether to enable serving the Nix store as a remote store via SSH.";
       };
 
       write = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable writing to the Nix store as a remote store via SSH. Note: the sshServe user is named nix-ssh and is not a trusted-user. nix-ssh should be added to the <option>nix.settings.trusted-users</option> option in most use cases, such as allowing remote building of derivations.";
+        description = lib.mdDoc "Whether to enable writing to the Nix store as a remote store via SSH. Note: the sshServe user is named nix-ssh and is not a trusted-user. nix-ssh should be added to the {option}`nix.settings.trusted-users` option in most use cases, such as allowing remote building of derivations.";
       };
 
       keys = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "ssh-dss AAAAB3NzaC1k... alice@example.org" ];
-        description = "A list of SSH public keys allowed to access the binary cache via SSH.";
+        description = lib.mdDoc "A list of SSH public keys allowed to access the binary cache via SSH.";
       };
 
       protocol = mkOption {
         type = types.enum [ "ssh" "ssh-ng" ];
         default = "ssh";
-        description = "The specific Nix-over-SSH protocol to use.";
+        description = lib.mdDoc "The specific Nix-over-SSH protocol to use.";
       };
 
     };
diff --git a/nixos/modules/services/misc/novacomd.nix b/nixos/modules/services/misc/novacomd.nix
index 7cfc68d2b673..bde8328d46f8 100644
--- a/nixos/modules/services/misc/novacomd.nix
+++ b/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/nixos/modules/services/misc/ntfy-sh.nix b/nixos/modules/services/misc/ntfy-sh.nix
new file mode 100644
index 000000000000..9d52fcf25364
--- /dev/null
+++ b/nixos/modules/services/misc/ntfy-sh.nix
@@ -0,0 +1,100 @@
+{ 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 ];
+      };
+
+      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;
+
+          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;
+        };
+      };
+
+      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/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix
index 27c5f2e395f6..d02fda62fa4f 100644
--- a/nixos/modules/services/misc/nzbget.nix
+++ b/nixos/modules/services/misc/nzbget.nix
@@ -25,26 +25,26 @@ in
 
   options = {
     services.nzbget = {
-      enable = mkEnableOption "NZBGet";
+      enable = mkEnableOption (lib.mdDoc "NZBGet");
 
       user = mkOption {
         type = types.str;
         default = "nzbget";
-        description = "User account under which NZBGet runs";
+        description = lib.mdDoc "User account under which NZBGet runs";
       };
 
       group = mkOption {
         type = types.str;
         default = "nzbget";
-        description = "Group under which NZBGet runs";
+        description = lib.mdDoc "Group under which NZBGet runs";
       };
 
       settings = mkOption {
         type = with types; attrsOf (oneOf [ bool int str ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           NZBGet configuration, passed via command line using switch -o. Refer to
-          <link xlink:href="https://github.com/nzbget/nzbget/blob/master/nzbget.conf"/>
+          <https://github.com/nzbget/nzbget/blob/master/nzbget.conf>
           for details on supported values.
         '';
         example = {
diff --git a/nixos/modules/services/misc/nzbhydra2.nix b/nixos/modules/services/misc/nzbhydra2.nix
index 500c40f117dd..47d08135f57e 100644
--- a/nixos/modules/services/misc/nzbhydra2.nix
+++ b/nixos/modules/services/misc/nzbhydra2.nix
@@ -7,26 +7,26 @@ let cfg = config.services.nzbhydra2;
 in {
   options = {
     services.nzbhydra2 = {
-      enable = mkEnableOption "NZBHydra2";
+      enable = mkEnableOption (lib.mdDoc "NZBHydra2");
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/nzbhydra2";
-        description = "The directory where NZBHydra2 stores its data files.";
+        description = lib.mdDoc "The directory where NZBHydra2 stores its data files.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
         description =
-          "Open ports in the firewall for the NZBHydra2 web interface.";
+          lib.mdDoc "Open ports in the firewall for the NZBHydra2 web interface.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.nzbhydra2;
         defaultText = literalExpression "pkgs.nzbhydra2";
-        description = "NZBHydra2 package to use.";
+        description = lib.mdDoc "NZBHydra2 package to use.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index cd846d3f268d..c216c6fa2b77 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/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,12 +29,12 @@ 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;
         default = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           Host to bind OctoPrint to.
         '';
       };
@@ -42,41 +42,47 @@ in
       port = mkOption {
         type = types.port;
         default = 5000;
-        description = ''
+        description = lib.mdDoc ''
           Port to bind OctoPrint to.
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Open ports in the firewall for OctoPrint.";
+      };
+
       user = mkOption {
         type = types.str;
         default = "octoprint";
-        description = "User for the daemon.";
+        description = lib.mdDoc "User for the daemon.";
       };
 
       group = mkOption {
         type = types.str;
         default = "octoprint";
-        description = "Group for the daemon.";
+        description = lib.mdDoc "Group for the daemon.";
       };
 
       stateDir = mkOption {
         type = types.path;
         default = "/var/lib/octoprint";
-        description = "State directory of the daemon.";
+        description = lib.mdDoc "State directory of the daemon.";
       };
 
       plugins = mkOption {
         type = types.functionTo (types.listOf types.package);
-        default = plugins: [];
+        default = plugins: [ ];
         defaultText = literalExpression "plugins: []";
         example = literalExpression "plugins: with plugins; [ themeify stlviewer ]";
-        description = "Additional plugins to be used. Available plugins are passed through the plugins input.";
+        description = lib.mdDoc "Additional plugins to be used. Available plugins are passed through the plugins input.";
       };
 
       extraConfig = mkOption {
         type = types.attrs;
-        default = {};
-        description = "Extra options which are added to OctoPrint's YAML configuration file.";
+        default = { };
+        description = lib.mdDoc "Extra options which are added to OctoPrint's YAML configuration file.";
       };
 
     };
@@ -128,6 +134,6 @@ in
       };
     };
 
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
   };
-
 }
diff --git a/nixos/modules/services/misc/ombi.nix b/nixos/modules/services/misc/ombi.nix
index b5882168e519..8bf6a9b116ec 100644
--- a/nixos/modules/services/misc/ombi.nix
+++ b/nixos/modules/services/misc/ombi.nix
@@ -7,40 +7,40 @@ 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;
         default = "/var/lib/ombi";
-        description = "The directory where Ombi stores its data files.";
+        description = lib.mdDoc "The directory where Ombi stores its data files.";
       };
 
       port = mkOption {
         type = types.port;
         default = 5000;
-        description = "The port for the Ombi web interface.";
+        description = lib.mdDoc "The port for the Ombi web interface.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the Ombi web interface.";
+        description = lib.mdDoc "Open ports in the firewall for the Ombi web interface.";
       };
 
       user = mkOption {
         type = types.str;
         default = "ombi";
-        description = "User account under which Ombi runs.";
+        description = lib.mdDoc "User account under which Ombi runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "ombi";
-        description = "Group under which Ombi runs.";
+        description = lib.mdDoc "Group under which Ombi runs.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/osrm.nix b/nixos/modules/services/misc/osrm.nix
index 79c347ab7e0e..12c908a761e3 100644
--- a/nixos/modules/services/misc/osrm.nix
+++ b/nixos/modules/services/misc/osrm.nix
@@ -11,44 +11,44 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable the OSRM service.";
+      description = lib.mdDoc "Enable the OSRM service.";
     };
 
     address = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = "IP address on which the web server will listen.";
+      description = lib.mdDoc "IP address on which the web server will listen.";
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 5000;
-      description = "Port on which the web server will run.";
+      description = lib.mdDoc "Port on which the web server will run.";
     };
 
     threads = mkOption {
       type = types.int;
       default = 4;
-      description = "Number of threads to use.";
+      description = lib.mdDoc "Number of threads to use.";
     };
 
     algorithm = mkOption {
       type = types.enum [ "CH" "CoreCH" "MLD" ];
       default = "MLD";
-      description = "Algorithm to use for the data. Must be one of CH, CoreCH, MLD";
+      description = lib.mdDoc "Algorithm to use for the data. Must be one of CH, CoreCH, MLD";
     };
 
     extraFlags = mkOption {
       type = types.listOf types.str;
       default = [];
       example = [ "--max-table-size 1000" "--max-matching-size 1000" ];
-      description = "Extra command line arguments passed to osrm-routed";
+      description = lib.mdDoc "Extra command line arguments passed to osrm-routed";
     };
 
     dataFile = mkOption {
       type = types.path;
       example = "/var/lib/osrm/berlin-latest.osrm";
-      description = "Data file location";
+      description = lib.mdDoc "Data file location";
     };
 
   };
diff --git a/nixos/modules/services/misc/owncast.nix b/nixos/modules/services/misc/owncast.nix
index 0852335238fd..01fe34cf50fe 100644
--- a/nixos/modules/services/misc/owncast.nix
+++ b/nixos/modules/services/misc/owncast.nix
@@ -5,12 +5,12 @@ in {
 
   options.services.owncast = {
 
-    enable = mkEnableOption "owncast";
+    enable = mkEnableOption (lib.mdDoc "owncast");
 
     dataDir = mkOption {
       type = types.str;
       default = "/var/lib/owncast";
-      description = ''
+      description = lib.mdDoc ''
         The directory where owncast stores its data files. If left as the default value this directory will automatically be created before the owncast server starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions.
       '';
     };
@@ -18,7 +18,7 @@ in {
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Open the appropriate ports in the firewall for owncast.
       '';
     };
@@ -26,26 +26,26 @@ in {
     user = mkOption {
       type = types.str;
       default = "owncast";
-      description = "User account under which owncast runs.";
+      description = lib.mdDoc "User account under which owncast runs.";
     };
 
     group = mkOption {
       type = types.str;
       default = "owncast";
-      description = "Group under which owncast runs.";
+      description = lib.mdDoc "Group under which owncast runs.";
     };
 
     listen = mkOption {
       type = types.str;
       default = "127.0.0.1";
       example = "0.0.0.0";
-      description = "The IP address to bind the owncast web server to.";
+      description = lib.mdDoc "The IP address to bind the owncast web server to.";
     };
 
     port = mkOption {
       type = types.port;
       default = 8080;
-      description = ''
+      description = lib.mdDoc ''
         TCP port where owncast web-gui listens.
       '';
     };
@@ -53,7 +53,7 @@ in {
     rtmp-port = mkOption {
       type = types.port;
       default = 1935;
-      description = ''
+      description = lib.mdDoc ''
         TCP port where owncast rtmp service listens.
       '';
     };
diff --git a/nixos/modules/services/misc/packagekit.nix b/nixos/modules/services/misc/packagekit.nix
index 9191078ef9ca..f3e6bf50e9b2 100644
--- a/nixos/modules/services/misc/packagekit.nix
+++ b/nixos/modules/services/misc/packagekit.nix
@@ -39,22 +39,22 @@ 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;
       default = { };
-      description = "Additional settings passed straight through to PackageKit.conf";
+      description = lib.mdDoc "Additional settings passed straight through to PackageKit.conf";
     };
 
     vendorSettings = mkOption {
       type = iniFmt.type;
       default = { };
-      description = "Additional settings passed straight through to Vendor.conf";
+      description = lib.mdDoc "Additional settings passed straight through to Vendor.conf";
     };
   };
 
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
index bfaf842fb464..6a98d5cb686d 100644
--- a/nixos/modules/services/misc/paperless.nix
+++ b/nixos/modules/services/misc/paperless.nix
@@ -3,6 +3,7 @@
 with lib;
 let
   cfg = config.services.paperless;
+  pkg = cfg.package;
 
   defaultUser = "paperless";
 
@@ -15,17 +16,19 @@ let
     PAPERLESS_MEDIA_ROOT = cfg.mediaDir;
     PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
     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 "$@"
+    exec ${pkg}/bin/paperless-ngx "$@"
   '';
 
   # Secure the services
@@ -77,13 +80,13 @@ 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";
   };
 in
 {
-  meta.maintainers = with maintainers; [ earvstedt Flakebi ];
+  meta.maintainers = with maintainers; [ erikarvstedt Flakebi ];
 
   imports = [
     (mkRenamedOptionModule [ "services" "paperless-ng" ] [ "services" "paperless" ])
@@ -93,7 +96,7 @@ in
     enable = mkOption {
       type = lib.types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable Paperless.
 
         When started, the Paperless database is automatically created if it doesn't
@@ -101,54 +104,54 @@ in
         Both tasks are achieved by running a Django migration.
 
         A script to manage the Paperless instance (by wrapping Django's manage.py) is linked to
-        <literal>''${dataDir}/paperless-manage</literal>.
+        `''${dataDir}/paperless-manage`.
       '';
     };
 
     dataDir = mkOption {
       type = types.str;
       default = "/var/lib/paperless";
-      description = "Directory to store the Paperless data.";
+      description = lib.mdDoc "Directory to store the Paperless data.";
     };
 
     mediaDir = mkOption {
       type = types.str;
       default = "${cfg.dataDir}/media";
       defaultText = literalExpression ''"''${dataDir}/media"'';
-      description = "Directory to store the Paperless documents.";
+      description = lib.mdDoc "Directory to store the Paperless documents.";
     };
 
     consumptionDir = mkOption {
       type = types.str;
       default = "${cfg.dataDir}/consume";
       defaultText = literalExpression ''"''${dataDir}/consume"'';
-      description = "Directory from which new documents are imported.";
+      description = lib.mdDoc "Directory from which new documents are imported.";
     };
 
     consumptionDirIsPublic = mkOption {
       type = types.bool;
       default = false;
-      description = "Whether all users can write to the consumption dir.";
+      description = lib.mdDoc "Whether all users can write to the consumption dir.";
     };
 
     passwordFile = mkOption {
       type = types.nullOr types.path;
       default = null;
       example = "/run/keys/paperless-password";
-      description = ''
+      description = lib.mdDoc ''
         A file containing the superuser password.
 
         A superuser is required to access the web interface.
         If unset, you can create a superuser manually by running
-        <literal>''${dataDir}/paperless-manage createsuperuser</literal>.
+        `''${dataDir}/paperless-manage createsuperuser`.
 
-        The default superuser name is <literal>admin</literal>. To change it, set
-        option <option>extraConfig.PAPERLESS_ADMIN_USER</option>.
+        The default superuser name is `admin`. To change it, set
+        option {option}`extraConfig.PAPERLESS_ADMIN_USER`.
         WARNING: When changing the superuser name after the initial setup, the old superuser
         will continue to exist.
 
         To disable login for the web interface, set the following:
-        <literal>extraConfig.PAPERLESS_AUTO_LOGIN_USERNAME = "admin";</literal>.
+        `extraConfig.PAPERLESS_AUTO_LOGIN_USERNAME = "admin";`.
         WARNING: Only use this on a trusted system without internet access to Paperless.
       '';
     };
@@ -156,42 +159,41 @@ in
     address = mkOption {
       type = types.str;
       default = "localhost";
-      description = "Web interface address.";
+      description = lib.mdDoc "Web interface address.";
     };
 
     port = mkOption {
       type = types.port;
       default = 28981;
-      description = "Web interface port.";
+      description = lib.mdDoc "Web interface port.";
     };
 
     extraConfig = mkOption {
       type = types.attrs;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Extra paperless config options.
 
-        See <link xlink:href="https://paperless-ngx.readthedocs.io/en/latest/configuration.html">the documentation</link>
+        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 {
       type = types.str;
       default = defaultUser;
-      description = "User under which Paperless runs.";
+      description = lib.mdDoc "User under which Paperless runs.";
     };
 
     package = mkOption {
       type = types.package;
       default = pkgs.paperless-ngx;
       defaultText = literalExpression "pkgs.paperless-ngx";
-      description = "The Paperless package to use.";
+      description = lib.mdDoc "The Paperless package to use.";
     };
   };
 
@@ -212,7 +214,7 @@ in
       description = "Paperless scheduler";
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = "${cfg.package}/bin/paperless-ngx qcluster";
+        ExecStart = "${pkg}/bin/paperless-ngx qcluster";
         Restart = "on-failure";
         # The `mbind` syscall is needed for running the classifier.
         SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "mbind" ];
@@ -228,9 +230,9 @@ in
 
         # 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"
+        if [[ $(cat "$versionFile" 2>/dev/null) != ${pkg} ]]; then
+          ${pkg}/bin/paperless-ngx migrate
+          echo ${pkg} > "$versionFile"
         fi
       ''
       + optionalString (cfg.passwordFile != null) ''
@@ -240,7 +242,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
       '';
@@ -265,7 +267,7 @@ in
       description = "Paperless document consumer";
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = "${cfg.package}/bin/paperless-ngx document_consumer";
+        ExecStart = "${pkg}/bin/paperless-ngx document_consumer";
         Restart = "on-failure";
       };
       environment = env;
@@ -280,21 +282,22 @@ in
       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.
diff --git a/nixos/modules/services/misc/parsoid.nix b/nixos/modules/services/misc/parsoid.nix
index 09b7f977bfbf..6f4a340c8a18 100644
--- a/nixos/modules/services/misc/parsoid.nix
+++ b/nixos/modules/services/misc/parsoid.nix
@@ -39,7 +39,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Parsoid -- bidirectional
           wikitext parser.
         '';
@@ -48,7 +48,7 @@ in
       wikis = mkOption {
         type = types.listOf (types.either types.str types.attrs);
         example = [ "http://localhost/api.php" ];
-        description = ''
+        description = lib.mdDoc ''
           Used MediaWiki API endpoints.
         '';
       };
@@ -56,7 +56,7 @@ in
       workers = mkOption {
         type = types.int;
         default = 2;
-        description = ''
+        description = lib.mdDoc ''
           Number of Parsoid workers.
         '';
       };
@@ -64,15 +64,15 @@ in
       interface = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           Interface to listen on.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8000;
-        description = ''
+        description = lib.mdDoc ''
           Port to listen on.
         '';
       };
@@ -80,7 +80,7 @@ in
       extraConfig = mkOption {
         type = types.attrs;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add to parsoid configuration.
         '';
       };
diff --git a/nixos/modules/services/misc/persistent-evdev.nix b/nixos/modules/services/misc/persistent-evdev.nix
new file mode 100644
index 000000000000..b1f367fec7fb
--- /dev/null
+++ b/nixos/modules/services/misc/persistent-evdev.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.persistent-evdev;
+  settingsFormat = pkgs.formats.json {};
+
+  configFile = settingsFormat.generate "persistent-evdev-config" {
+    cache = "/var/cache/persistent-evdev";
+    devices = lib.mapAttrs (virt: phys: "/dev/input/by-id/${phys}") cfg.devices;
+  };
+in
+{
+  options.services.persistent-evdev = {
+    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 = lib.mdDoc ''
+        A set of virtual proxy device labels with backing physical device ids.
+
+        Physical devices should already exist in {file}`/dev/input/by-id/`.
+        Proxy devices will be automatically given a `uinput-` prefix.
+
+        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 `uinput-*` devices to the qemu
+        `cgroup_device_acl` list (see [](#opt-virtualisation.libvirtd.qemu.verbatimConfig)).
+      '';
+      example = lib.literalExpression ''
+        {
+          persist-mouse0 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-event-if01";
+          persist-mouse1 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-event-mouse";
+          persist-mouse2 = "usb-Logitech_G403_Prodigy_Gaming_Mouse_078738533531-if01-event-kbd";
+          persist-keyboard0 = "usb-Microsoft_Natural®_Ergonomic_Keyboard_4000-event-kbd";
+          persist-keyboard1 = "usb-Microsoft_Natural®_Ergonomic_Keyboard_4000-if01-event-kbd";
+        }
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.services.persistent-evdev = {
+      documentation = [ "https://github.com/aiberia/persistent-evdev/blob/master/README.md" ];
+      description = "Persistent evdev proxy";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Restart = "on-failure";
+        ExecStart = "${pkgs.persistent-evdev}/bin/persistent-evdev.py ${configFile}";
+        CacheDirectory = "persistent-evdev";
+      };
+    };
+
+    services.udev.packages = [ pkgs.persistent-evdev ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ lodi ];
+}
diff --git a/nixos/modules/services/misc/pinnwand.nix b/nixos/modules/services/misc/pinnwand.nix
index cbc796c9a7c8..5fca9f4125a8 100644
--- a/nixos/modules/services/misc/pinnwand.nix
+++ b/nixos/modules/services/misc/pinnwand.nix
@@ -10,38 +10,75 @@ let
 in
 {
   options.services.pinnwand = {
-    enable = mkEnableOption "Pinnwand";
+    enable = mkEnableOption (lib.mdDoc "Pinnwand");
 
     port = mkOption {
       type = types.port;
-      description = "The port to listen on.";
+      description = lib.mdDoc "The port to listen on.";
       default = 8000;
     };
 
     settings = mkOption {
-      type = format.type;
-      description = ''
-        Your <filename>pinnwand.toml</filename> as a Nix attribute set. Look up
-        possible options in the <link xlink:href="https://github.com/supakeen/pinnwand/blob/master/pinnwand.toml-example">pinnwand.toml-example</link>.
-      '';
       default = {};
+      description = lib.mdDoc ''
+        Your {file}`pinnwand.toml` as a Nix attribute set. Look up
+        possible options in the [documentation](https://pinnwand.readthedocs.io/en/v${pkgs.pinnwand.version}/configuration.html).
+      '';
+      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/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index 1cd8da768f48..7fc76028c02a 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -12,12 +12,12 @@ in
 
   options = {
     services.plex = {
-      enable = mkEnableOption "Plex Media Server";
+      enable = mkEnableOption (lib.mdDoc "Plex Media Server");
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/plex";
-        description = ''
+        description = lib.mdDoc ''
           The directory where Plex stores its data files.
         '';
       };
@@ -25,7 +25,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the media server.
         '';
       };
@@ -33,7 +33,7 @@ in
       user = mkOption {
         type = types.str;
         default = "plex";
-        description = ''
+        description = lib.mdDoc ''
           User account under which Plex runs.
         '';
       };
@@ -41,7 +41,7 @@ in
       group = mkOption {
         type = types.str;
         default = "plex";
-        description = ''
+        description = lib.mdDoc ''
           Group under which Plex runs.
         '';
       };
@@ -49,7 +49,7 @@ in
       extraPlugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of paths to extra plugin bundles to install in Plex's plugin
           directory. Every time the systemd unit for Plex starts up, all of the
           symlinks in Plex's plugin directory will be cleared and this module
@@ -73,7 +73,7 @@ in
       extraScanners = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of paths to extra scanners to install in Plex's scanners
           directory.
 
@@ -97,7 +97,7 @@ in
         type = types.package;
         default = pkgs.plex;
         defaultText = literalExpression "pkgs.plex";
-        description = ''
+        description = lib.mdDoc ''
           The Plex package to use. Plex subscribers may wish to use their own
           package here, pointing to subscriber-only server versions.
         '';
@@ -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/nixos/modules/services/misc/plikd.nix b/nixos/modules/services/misc/plikd.nix
index a62dbef1d2af..9b0825bf40c9 100644
--- a/nixos/modules/services/misc/plikd.nix
+++ b/nixos/modules/services/misc/plikd.nix
@@ -11,19 +11,19 @@ in
 {
   options = {
     services.plikd = {
-      enable = mkEnableOption "the plikd server";
+      enable = mkEnableOption (lib.mdDoc "the plikd server");
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the plikd.";
+        description = lib.mdDoc "Open ports in the firewall for the plikd.";
       };
 
       settings = mkOption {
         type = format.type;
         default = {};
-        description = ''
-          Configuration for plikd, see <link xlink:href="https://github.com/root-gg/plik/blob/master/server/plikd.cfg"/>
+        description = lib.mdDoc ''
+          Configuration for plikd, see <https://github.com/root-gg/plik/blob/master/server/plikd.cfg>
           for supported values.
         '';
       };
diff --git a/nixos/modules/services/misc/podgrab.nix b/nixos/modules/services/misc/podgrab.nix
index 7077408b7942..c596122fd31c 100644
--- a/nixos/modules/services/misc/podgrab.nix
+++ b/nixos/modules/services/misc/podgrab.nix
@@ -4,15 +4,15 @@ 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;
       default = null;
       example = "/run/secrets/password.env";
-      description = ''
+      description = lib.mdDoc ''
         The path to a file containing the PASSWORD environment variable
-        definition for Podgrab's authentification.
+        definition for Podgrab's authentication.
       '';
     };
 
@@ -20,7 +20,7 @@ in
       type = types.port;
       default = 8080;
       example = 4242;
-      description = "The port on which Podgrab will listen for incoming HTTP traffic.";
+      description = lib.mdDoc "The port on which Podgrab will listen for incoming HTTP traffic.";
     };
   };
 
@@ -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/nixos/modules/services/misc/polaris.nix b/nixos/modules/services/misc/polaris.nix
new file mode 100644
index 000000000000..83da486083b4
--- /dev/null
+++ b/nixos/modules/services/misc/polaris.nix
@@ -0,0 +1,151 @@
+{ config
+, pkgs
+, lib
+, ...}:
+
+with lib;
+let
+  cfg = config.services.polaris;
+  settingsFormat = pkgs.formats.toml {};
+in
+{
+  options = {
+    services.polaris = {
+      enable = mkEnableOption (lib.mdDoc "Polaris Music Server");
+
+      package = mkPackageOption pkgs "polaris" { };
+
+      user = mkOption {
+        type = types.str;
+        default = "polaris";
+        description = lib.mdDoc "User account under which Polaris runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "polaris";
+        description = lib.mdDoc "Group under which Polaris is run.";
+      };
+
+      extraGroups = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = lib.mdDoc "Polaris' auxiliary groups.";
+        example = literalExpression ''["media" "music"]'';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 5050;
+        description = lib.mdDoc ''
+          The port which the Polaris REST api and web UI should listen to.
+          Note: polaris is hardcoded to listen to the hostname "0.0.0.0".
+        '';
+      };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = {};
+        description = lib.mdDoc ''
+          Contents for the TOML Polaris config, applied each start.
+          Although poorly documented, an example may be found here:
+          [test-config.toml](https://github.com/agersant/polaris/blob/374d0ca56fc0a466d797a4b252e2078607476797/test-data/config.toml)
+        '';
+        example = literalExpression ''
+          {
+            settings.reindex_every_n_seconds = 7*24*60*60; # weekly, default is 1800
+            settings.album_art_pattern =
+              "(cover|front|folder)\.(jpeg|jpg|png|bmp|gif)";
+            mount_dirs = [
+              {
+                name = "NAS";
+                source = "/mnt/nas/music";
+              }
+              {
+                name = "Local";
+                source = "/home/my_user/Music";
+              }
+            ];
+          }
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open the configured port in the firewall.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.polaris = {
+      description = "Polaris Music Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = rec {
+        User = cfg.user;
+        Group = cfg.group;
+        DynamicUser = true;
+        SupplementaryGroups = cfg.extraGroups;
+        StateDirectory = "polaris";
+        CacheDirectory = "polaris";
+        ExecStart = escapeShellArgs ([
+          "${cfg.package}/bin/polaris"
+          "--foreground"
+          "--port" cfg.port
+          "--database" "/var/lib/${StateDirectory}/db.sqlite"
+          "--cache" "/var/cache/${CacheDirectory}"
+        ] ++ optionals (cfg.settings != {}) [
+          "--config" (settingsFormat.generate "polaris-config.toml" cfg.settings)
+        ]);
+        Restart = "on-failure";
+
+        # Security options:
+
+        #NoNewPrivileges = true; # implied by DynamicUser
+        #RemoveIPC = true; # implied by DynamicUser
+
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+
+        DeviceAllow = "";
+
+        LockPersonality = true;
+
+        #PrivateTmp = true; # implied by DynamicUser
+        PrivateDevices = true;
+        PrivateUsers = true;
+
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+
+        RestrictNamespaces = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictRealtime = true;
+        #RestrictSUIDSGID = true; # implied by DynamicUser
+
+        SystemCallArchitectures = "native";
+        SystemCallErrorNumber = "EPERM";
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
+        ];
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ pbsds ];
+}
diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix
new file mode 100644
index 000000000000..f60cbe347713
--- /dev/null
+++ b/nixos/modules/services/misc/portunus.nix
@@ -0,0 +1,288 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.portunus;
+
+in
+{
+  options.services.portunus = {
+    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 = lib.mdDoc "Subdomain which gets reverse proxied to Portunus webserver.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      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.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.portunus;
+      defaultText = lib.literalExpression "pkgs.portunus";
+      description = lib.mdDoc "The Portunus package to use.";
+    };
+
+    seedPath = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Path to a portunus seed file in json format.
+        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 = lib.mdDoc "Path where Portunus stores its state.";
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "portunus";
+      description = lib.mdDoc "User account under which Portunus runs its webserver.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "portunus";
+      description = lib.mdDoc "Group account under which Portunus runs its webserver.";
+    };
+
+    dex = {
+      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 `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 = lib.mdDoc "URL where the OIDC client should redirect";
+            };
+            id = mkOption {
+              type = types.str;
+              description = lib.mdDoc "ID of the OIDC client";
+            };
+          };
+        });
+        default = [ ];
+        example = [
+          {
+            callbackURL = "https://example.com/client/oidc/callback";
+            id = "service";
+          }
+        ];
+        description = lib.mdDoc ''
+          List of OIDC clients.
+
+          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 = lib.mdDoc "Port where dex should listen on.";
+      };
+    };
+
+    ldap = {
+      package = mkOption {
+        type = types.package;
+        default = pkgs.openldap;
+        defaultText = lib.literalExpression "pkgs.openldap";
+        description = lib.mdDoc "The OpenLDAP package to use.";
+      };
+
+      searchUserName = mkOption {
+        type = types.str;
+        default = "";
+        example = "admin";
+        description = lib.mdDoc ''
+          The login name of the search user.
+          This user account must be configured in Portunus either manually or via seeding.
+        '';
+      };
+
+      suffix = mkOption {
+        type = types.str;
+        example = "dc=example,dc=org";
+        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.
+        '';
+      };
+
+      tls = mkOption {
+        type = types.bool;
+        default = false;
+        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 [](#opt-services.portunus.domain) to be configured via [](#opt-security.acme.certs).
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "openldap";
+        description = lib.mdDoc "User account under which Portunus runs its LDAP server.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "openldap";
+        description = lib.mdDoc "Group account under which Portunus runs its LDAP server.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.dex.enable -> cfg.ldap.searchUserName != "";
+        message = "services.portunus.dex.enable requires services.portunus.ldap.searchUserName to be set.";
+      }
+    ];
+
+    # add ldapsearch(1) etc. to interactive shells
+    environment.systemPackages = [ cfg.ldap.package ];
+
+    # allow connecting via ldaps /w certificate without opening ports
+    networking.hosts = mkIf cfg.ldap.tls {
+      "::1" = [ cfg.domain ];
+      "127.0.0.1" = [ cfg.domain ];
+    };
+
+    services.dex = mkIf cfg.dex.enable {
+      enable = true;
+      settings = {
+        issuer = "https://${cfg.domain}/dex";
+        web.http = "127.0.0.1:${toString cfg.dex.port}";
+        storage = {
+          type = "sqlite3";
+          config.file = "/var/lib/dex/dex.db";
+        };
+        enablePasswordDB = false;
+        connectors = [{
+          type = "ldap";
+          id = "ldap";
+          name = "LDAP";
+          config = {
+            host = "${cfg.domain}:636";
+            bindDN = "uid=${cfg.ldap.searchUserName},ou=users,${cfg.ldap.suffix}";
+            bindPW = "$DEX_SEARCH_USER_PASSWORD";
+            userSearch = {
+              baseDN = "ou=users,${cfg.ldap.suffix}";
+              filter = "(objectclass=person)";
+              username = "uid";
+              idAttr = "uid";
+              emailAttr = "mail";
+              nameAttr = "cn";
+              preferredUsernameAttr = "uid";
+            };
+            groupSearch = {
+              baseDN = "ou=groups,${cfg.ldap.suffix}";
+              filter = "(objectclass=groupOfNames)";
+              nameAttr = "cn";
+              userMatchers = [{ userAttr = "DN"; groupAttr = "member"; }];
+            };
+          };
+        }];
+
+        staticClients = forEach cfg.dex.oidcClients (client: {
+          inherit (client) id;
+          redirectURIs = [ client.callbackURL ];
+          name = "OIDC for ${client.id}";
+          secretEnv = "DEX_CLIENT_${client.id}";
+        });
+      };
+    };
+
+    systemd.services = {
+      dex.serviceConfig = mkIf cfg.dex.enable {
+        # `dex.service` is super locked down out of the box, but we need some
+        # place to write the SQLite database. This creates $STATE_DIRECTORY below
+        # /var/lib/private because DynamicUser=true, but it gets symlinked into
+        # /var/lib/dex inside the unit
+        StateDirectory = "dex";
+      };
+
+      portunus = {
+        description = "Self-contained authentication service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig.ExecStart = "${cfg.package.out}/bin/portunus-orchestrator";
+        environment = {
+          PORTUNUS_LDAP_SUFFIX = cfg.ldap.suffix;
+          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_STATE_DIR = cfg.stateDir;
+          PORTUNUS_SLAPD_BINARY = "${cfg.ldap.package}/libexec/slapd";
+          PORTUNUS_SLAPD_GROUP = cfg.ldap.group;
+          PORTUNUS_SLAPD_USER = cfg.ldap.user;
+          PORTUNUS_SLAPD_SCHEMA_DIR = "${cfg.ldap.package}/etc/schema";
+        } // (optionalAttrs (cfg.seedPath != null) ({
+          PORTUNUS_SEED_PATH = cfg.seedPath;
+        })) // (optionalAttrs cfg.ldap.tls (
+          let
+            acmeDirectory = config.security.acme.certs."${cfg.domain}".directory;
+          in
+          {
+            PORTUNUS_SLAPD_TLS_CA_CERTIFICATE = "/etc/ssl/certs/ca-certificates.crt";
+            PORTUNUS_SLAPD_TLS_CERTIFICATE = "${acmeDirectory}/cert.pem";
+            PORTUNUS_SLAPD_TLS_DOMAIN_NAME = cfg.domain;
+            PORTUNUS_SLAPD_TLS_PRIVATE_KEY = "${acmeDirectory}/key.pem";
+          }));
+      };
+    };
+
+    users.users = mkMerge [
+      (mkIf (cfg.ldap.user == "openldap") {
+        openldap = {
+          group = cfg.ldap.group;
+          isSystemUser = true;
+        };
+      })
+      (mkIf (cfg.user == "portunus") {
+        portunus = {
+          group = cfg.group;
+          isSystemUser = true;
+        };
+      })
+    ];
+
+    users.groups = mkMerge [
+      (mkIf (cfg.ldap.user == "openldap") {
+        openldap = { };
+      })
+      (mkIf (cfg.user == "portunus") {
+        portunus = { };
+      })
+    ];
+  };
+
+  meta.maintainers = [ maintainers.majewsky ] ++ teams.c3d2.members;
+}
diff --git a/nixos/modules/services/misc/prowlarr.nix b/nixos/modules/services/misc/prowlarr.nix
index ef820b4022d5..77b8ec989479 100644
--- a/nixos/modules/services/misc/prowlarr.nix
+++ b/nixos/modules/services/misc/prowlarr.nix
@@ -9,12 +9,12 @@ in
 {
   options = {
     services.prowlarr = {
-      enable = mkEnableOption "Prowlarr";
+      enable = mkEnableOption (lib.mdDoc "Prowlarr");
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the Prowlarr web interface.";
+        description = lib.mdDoc "Open ports in the firewall for the Prowlarr web interface.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix
index 2f752bcc7ed6..314388e0152e 100644
--- a/nixos/modules/services/misc/pykms.nix
+++ b/nixos/modules/services/misc/pykms.nix
@@ -18,43 +18,43 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the PyKMS service.";
+        description = lib.mdDoc "Whether to enable the PyKMS service.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "The IP address on which to listen.";
+        description = lib.mdDoc "The IP address on which to listen.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 1688;
-        description = "The port on which to listen.";
+        description = lib.mdDoc "The port on which to listen.";
       };
 
       openFirewallPort = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether the listening port should be opened automatically.";
+        description = lib.mdDoc "Whether the listening port should be opened automatically.";
       };
 
       memoryLimit = mkOption {
         type = types.str;
         default = "64M";
-        description = "How much memory to use at most.";
+        description = lib.mdDoc "How much memory to use at most.";
       };
 
       logLevel = mkOption {
         type = types.enum [ "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG" "MININFO" ];
         default = "INFO";
-        description = "How much to log";
+        description = lib.mdDoc "How much to log";
       };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = "Additional arguments";
+        description = lib.mdDoc "Additional arguments";
       };
     };
   };
diff --git a/nixos/modules/services/misc/radarr.nix b/nixos/modules/services/misc/radarr.nix
index 74444e24043f..834b092c0d14 100644
--- a/nixos/modules/services/misc/radarr.nix
+++ b/nixos/modules/services/misc/radarr.nix
@@ -9,30 +9,38 @@ in
 {
   options = {
     services.radarr = {
-      enable = mkEnableOption "Radarr";
+      enable = mkEnableOption (lib.mdDoc "Radarr");
+
+      package = mkOption {
+        description = lib.mdDoc "Radarr package to use";
+        default = pkgs.radarr;
+        defaultText = literalExpression "pkgs.radarr";
+        example = literalExpression "pkgs.radarr";
+        type = types.package;
+      };
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/radarr/.config/Radarr";
-        description = "The directory where Radarr stores its data files.";
+        description = lib.mdDoc "The directory where Radarr stores its data files.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the Radarr web interface.";
+        description = lib.mdDoc "Open ports in the firewall for the Radarr web interface.";
       };
 
       user = mkOption {
         type = types.str;
         default = "radarr";
-        description = "User account under which Radarr runs.";
+        description = lib.mdDoc "User account under which Radarr runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "radarr";
-        description = "Group under which Radarr runs.";
+        description = lib.mdDoc "Group under which Radarr runs.";
       };
     };
   };
@@ -51,7 +59,7 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${pkgs.radarr}/bin/Radarr -nobrowser -data='${cfg.dataDir}'";
+        ExecStart = "${cfg.package}/bin/Radarr -nobrowser -data='${cfg.dataDir}'";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 696b8d1a25d9..58a595b5c76f 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -49,46 +49,46 @@ in
   # interface
   options = {
     services.redmine = {
-      enable = mkEnableOption "Redmine";
+      enable = mkEnableOption (lib.mdDoc "Redmine");
 
       package = mkOption {
         type = types.package;
         default = pkgs.redmine;
         defaultText = literalExpression "pkgs.redmine";
-        description = "Which Redmine package to use.";
+        description = lib.mdDoc "Which Redmine package to use.";
         example = literalExpression "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
       };
 
       user = mkOption {
         type = types.str;
         default = "redmine";
-        description = "User under which Redmine is ran.";
+        description = lib.mdDoc "User under which Redmine is ran.";
       };
 
       group = mkOption {
         type = types.str;
         default = "redmine";
-        description = "Group under which Redmine is ran.";
+        description = lib.mdDoc "Group under which Redmine is ran.";
       };
 
       port = mkOption {
         type = types.port;
         default = 3000;
-        description = "Port on which Redmine is ran.";
+        description = lib.mdDoc "Port on which Redmine is ran.";
       };
 
       stateDir = mkOption {
         type = types.str;
         default = "/var/lib/redmine";
-        description = "The state directory, logs and plugins are stored here.";
+        description = lib.mdDoc "The state directory, logs and plugins are stored here.";
       };
 
       settings = mkOption {
         type = format.type;
         default = {};
-        description = ''
-          Redmine configuration (<filename>configuration.yml</filename>). Refer to
-          <link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
+        description = lib.mdDoc ''
+          Redmine configuration ({file}`configuration.yml`). Refer to
+          <https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration>
           for details.
         '';
         example = literalExpression ''
@@ -107,10 +107,10 @@ in
       extraEnv = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration in additional_environment.rb.
 
-          See <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
+          See <https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example>
           for details.
         '';
         example = ''
@@ -121,7 +121,7 @@ in
       themes = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = "Set of themes.";
+        description = lib.mdDoc "Set of themes.";
         example = literalExpression ''
           {
             dkuk-redmine_alex_skin = builtins.fetchurl {
@@ -135,7 +135,7 @@ in
       plugins = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = "Set of plugins.";
+        description = lib.mdDoc "Set of plugins.";
         example = literalExpression ''
           {
             redmine_env_auth = builtins.fetchurl {
@@ -151,41 +151,41 @@ in
           type = types.enum [ "mysql2" "postgresql" ];
           example = "postgresql";
           default = "mysql2";
-          description = "Database engine to use.";
+          description = lib.mdDoc "Database engine to use.";
         };
 
         host = mkOption {
           type = types.str;
           default = "localhost";
-          description = "Database host address.";
+          description = lib.mdDoc "Database host address.";
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = if cfg.database.type == "postgresql" then 5432 else 3306;
           defaultText = literalExpression "3306";
-          description = "Database host port.";
+          description = lib.mdDoc "Database host port.";
         };
 
         name = mkOption {
           type = types.str;
           default = "redmine";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = mkOption {
           type = types.str;
           default = "redmine";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/redmine-dbpassword";
-          description = ''
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
 
@@ -197,13 +197,64 @@ in
             else null;
           defaultText = literalExpression "/run/mysqld/mysqld.sock";
           example = "/run/mysqld/mysqld.sock";
-          description = "Path to the unix socket file to use for authentication.";
+          description = lib.mdDoc "Path to the unix socket file to use for authentication.";
         };
 
         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.";
+        };
+      };
+
+      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 = if cfg.components.subversion then "${pkgs.subversion}/bin/svn" else "";
+        scm_mercurial_command = if cfg.components.mercurial then "${pkgs.mercurial}/bin/hg" else "";
+        scm_git_command = if cfg.components.git then "${pkgs.git}/bin/git" else "";
+        scm_cvs_command = if cfg.components.cvs then "${pkgs.cvs}/bin/cvs" else "";
+        scm_bazaar_command = if cfg.components.breezy then "${pkgs.breezy}/bin/bzr" else "";
+        imagemagick_convert_command = if cfg.components.imagemagick then "${pkgs.imagemagick}/bin/convert" else "";
+        gs_command = if cfg.components.ghostscript then "${pkgs.ghostscript}/bin/gs" else "";
+        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/"*
diff --git a/nixos/modules/services/misc/ripple-data-api.nix b/nixos/modules/services/misc/ripple-data-api.nix
index 93eba98b7d30..30623a321338 100644
--- a/nixos/modules/services/misc/ripple-data-api.nix
+++ b/nixos/modules/services/misc/ripple-data-api.nix
@@ -35,92 +35,92 @@ let
 in {
   options = {
     services.rippleDataApi = {
-      enable = mkEnableOption "ripple data api";
+      enable = mkEnableOption (lib.mdDoc "ripple data api");
 
       port = mkOption {
-        description = "Ripple data api port";
+        description = lib.mdDoc "Ripple data api port";
         default = 5993;
-        type = types.int;
+        type = types.port;
       };
 
       importMode = mkOption {
-        description = "Ripple data api import mode.";
+        description = lib.mdDoc "Ripple data api import mode.";
         default = "liveOnly";
         type = types.enum ["live" "liveOnly"];
       };
 
       minLedger = mkOption {
-        description = "Ripple data api minimal ledger to fetch.";
+        description = lib.mdDoc "Ripple data api minimal ledger to fetch.";
         default = null;
         type = types.nullOr types.int;
       };
 
       maxLedger = mkOption {
-        description = "Ripple data api maximal ledger to fetch.";
+        description = lib.mdDoc "Ripple data api maximal ledger to fetch.";
         default = null;
         type = types.nullOr types.int;
       };
 
       redis = {
         enable = mkOption {
-          description = "Whether to enable caching of ripple data to redis.";
+          description = lib.mdDoc "Whether to enable caching of ripple data to redis.";
           default = true;
           type = types.bool;
         };
 
         host = mkOption {
-          description = "Ripple data api redis host.";
+          description = lib.mdDoc "Ripple data api redis host.";
           default = "localhost";
           type = types.str;
         };
 
         port = mkOption {
-          description = "Ripple data api redis port.";
+          description = lib.mdDoc "Ripple data api redis port.";
           default = 5984;
-          type = types.int;
+          type = types.port;
         };
       };
 
       couchdb = {
         host = mkOption {
-          description = "Ripple data api couchdb host.";
+          description = lib.mdDoc "Ripple data api couchdb host.";
           default = "localhost";
           type = types.str;
         };
 
         port = mkOption {
-          description = "Ripple data api couchdb port.";
+          description = lib.mdDoc "Ripple data api couchdb port.";
           default = 5984;
-          type = types.int;
+          type = types.port;
         };
 
         db = mkOption {
-          description = "Ripple data api couchdb database.";
+          description = lib.mdDoc "Ripple data api couchdb database.";
           default = "rippled";
           type = types.str;
         };
 
         user = mkOption {
-          description = "Ripple data api couchdb username.";
+          description = lib.mdDoc "Ripple data api couchdb username.";
           default = "rippled";
           type = types.str;
         };
 
         pass = mkOption {
-          description = "Ripple data api couchdb password.";
+          description = lib.mdDoc "Ripple data api couchdb password.";
           default = "";
           type = types.str;
         };
 
         create = mkOption {
-          description = "Whether to create couchdb database needed by ripple data api.";
+          description = lib.mdDoc "Whether to create couchdb database needed by ripple data api.";
           type = types.bool;
           default = true;
         };
       };
 
       rippleds = mkOption {
-        description = "List of rippleds to be used by ripple data api.";
+        description = lib.mdDoc "List of rippleds to be used by ripple data api.";
         default = [
           "http://s_east.ripple.com:51234"
           "http://s_west.ripple.com:51234"
diff --git a/nixos/modules/services/misc/rippled.nix b/nixos/modules/services/misc/rippled.nix
index f6ec0677774b..d14b6421b742 100644
--- a/nixos/modules/services/misc/rippled.nix
+++ b/nixos/modules/services/misc/rippled.nix
@@ -92,41 +92,41 @@ let
 
       ip = mkOption {
         default = "127.0.0.1";
-        description = "Ip where rippled listens.";
+        description = lib.mdDoc "Ip where rippled listens.";
         type = types.str;
       };
 
       port = mkOption {
-        description = "Port where rippled listens.";
-        type = types.int;
+        description = lib.mdDoc "Port where rippled listens.";
+        type = types.port;
       };
 
       protocol = mkOption {
-        description = "Protocols expose by rippled.";
+        description = lib.mdDoc "Protocols expose by rippled.";
         type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
       };
 
       user = mkOption {
-        description = "When set, these credentials will be required on HTTP/S requests.";
+        description = lib.mdDoc "When set, these credentials will be required on HTTP/S requests.";
         type = types.str;
         default = "";
       };
 
       password = mkOption {
-        description = "When set, these credentials will be required on HTTP/S requests.";
+        description = lib.mdDoc "When set, these credentials will be required on HTTP/S requests.";
         type = types.str;
         default = "";
       };
 
       admin = mkOption {
-        description = "A comma-separated list of admin IP addresses.";
+        description = lib.mdDoc "A comma-separated list of admin IP addresses.";
         type = types.listOf types.str;
         default = ["127.0.0.1"];
       };
 
       ssl = {
         key = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Specifies the filename holding the SSL key in PEM format.
           '';
           default = null;
@@ -134,7 +134,7 @@ let
         };
 
         cert = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Specifies the path to the SSL certificate file in PEM format.
             This is not needed if the chain includes it.
           '';
@@ -143,7 +143,7 @@ let
         };
 
         chain = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             If you need a certificate chain, specify the path to the
             certificate chain here. The chain may include the end certificate.
           '';
@@ -157,33 +157,33 @@ let
   dbOptions = {
     options = {
       type = mkOption {
-        description = "Rippled database type.";
+        description = lib.mdDoc "Rippled database type.";
         type = types.enum ["rocksdb" "nudb"];
         default = "rocksdb";
       };
 
       path = mkOption {
-        description = "Location to store the database.";
+        description = lib.mdDoc "Location to store the database.";
         type = types.path;
         default = cfg.databasePath;
         defaultText = literalExpression "config.${opt.databasePath}";
       };
 
       compression = mkOption {
-        description = "Whether to enable snappy compression.";
+        description = lib.mdDoc "Whether to enable snappy compression.";
         type = types.nullOr types.bool;
         default = null;
       };
 
       onlineDelete = mkOption {
-        description = "Enable automatic purging of older ledger information.";
+        description = lib.mdDoc "Enable automatic purging of older ledger information.";
         type = types.nullOr (types.addCheck types.int (v: v > 256));
         default = cfg.ledgerHistory;
         defaultText = literalExpression "config.${opt.ledgerHistory}";
       };
 
       advisoryDelete = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           If set, then require administrative RPC call "can_delete"
           to enable online deletion of ledger records.
         '';
@@ -192,7 +192,7 @@ let
       };
 
       extraOpts = mkOption {
-        description = "Extra database options.";
+        description = lib.mdDoc "Extra database options.";
         type = types.lines;
         default = "";
       };
@@ -207,17 +207,17 @@ in
 
   options = {
     services.rippled = {
-      enable = mkEnableOption "rippled";
+      enable = mkEnableOption (lib.mdDoc "rippled");
 
       package = mkOption {
-        description = "Which rippled package to use.";
+        description = lib.mdDoc "Which rippled package to use.";
         type = types.package;
         default = pkgs.rippled;
         defaultText = literalExpression "pkgs.rippled";
       };
 
       ports = mkOption {
-        description = "Ports exposed by rippled";
+        description = lib.mdDoc "Ports exposed by rippled";
         type = with types; attrsOf (submodule portOptions);
         default = {
           rpc = {
@@ -241,7 +241,7 @@ in
       };
 
       nodeDb = mkOption {
-        description = "Rippled main database options.";
+        description = lib.mdDoc "Rippled main database options.";
         type = with types; nullOr (submodule dbOptions);
         default = {
           type = "rocksdb";
@@ -256,19 +256,19 @@ in
       };
 
       tempDb = mkOption {
-        description = "Rippled temporary database options.";
+        description = lib.mdDoc "Rippled temporary database options.";
         type = with types; nullOr (submodule dbOptions);
         default = null;
       };
 
       importDb = mkOption {
-        description = "Settings for performing a one-time import.";
+        description = lib.mdDoc "Settings for performing a one-time import.";
         type = with types; nullOr (submodule dbOptions);
         default = null;
       };
 
       nodeSize = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Rippled size of the node you are running.
           "tiny", "small", "medium", "large", and "huge"
         '';
@@ -277,7 +277,7 @@ in
       };
 
       ips = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           List of hostnames or ips where the Ripple protocol is served.
           For a starter list, you can either copy entries from:
           https://ripple.com/ripple.txt or if you prefer you can let it
@@ -292,7 +292,7 @@ in
       };
 
       ipsFixed = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           List of IP addresses or hostnames to which rippled should always
           attempt to maintain peer connections with. This is useful for
           manually forming private networks, for example to configure a
@@ -306,7 +306,7 @@ in
       };
 
       validators = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           List of nodes to always accept as validators. Nodes are specified by domain
           or public key.
         '';
@@ -321,7 +321,7 @@ in
       };
 
       databasePath = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Path to the ripple database.
         '';
         type = types.path;
@@ -329,7 +329,7 @@ in
       };
 
       validationQuorum = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The minimum number of trusted validations a ledger must have before
           the server considers it fully validated.
         '';
@@ -338,7 +338,7 @@ in
       };
 
       ledgerHistory = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The number of past ledgers to acquire on server startup and the minimum
           to maintain while running.
         '';
@@ -347,7 +347,7 @@ in
       };
 
       fetchDepth = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The number of past ledgers to serve to other peers that request historical
           ledger data (or "full" for no limit).
         '';
@@ -356,7 +356,7 @@ in
       };
 
       sntpServers = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           IP address or domain of NTP servers to use for time synchronization.;
         '';
         type = types.listOf types.str;
@@ -369,22 +369,22 @@ in
       };
 
       logLevel = mkOption {
-        description = "Logging verbosity.";
+        description = lib.mdDoc "Logging verbosity.";
         type = types.enum ["debug" "error" "info"];
         default = "error";
       };
 
       statsd = {
-        enable = mkEnableOption "statsd monitoring for rippled";
+        enable = mkEnableOption (lib.mdDoc "statsd monitoring for rippled");
 
         address = mkOption {
-          description = "The UDP address and port of the listening StatsD server.";
+          description = lib.mdDoc "The UDP address and port of the listening StatsD server.";
           default = "127.0.0.1:8125";
           type = types.str;
         };
 
         prefix = mkOption {
-          description = "A string prepended to each collected metric.";
+          description = lib.mdDoc "A string prepended to each collected metric.";
           default = "";
           type = types.str;
         };
@@ -393,7 +393,7 @@ in
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the rippled.cfg configuration file.
         '';
       };
@@ -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/nixos/modules/services/misc/rmfakecloud.nix b/nixos/modules/services/misc/rmfakecloud.nix
index fe522653c216..1cdfdeceabcd 100644
--- a/nixos/modules/services/misc/rmfakecloud.nix
+++ b/nixos/modules/services/misc/rmfakecloud.nix
@@ -9,13 +9,13 @@ 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;
         default = pkgs.rmfakecloud;
         defaultText = literalExpression "pkgs.rmfakecloud";
-        description = ''
+        description = lib.mdDoc ''
           rmfakecloud package to use.
 
           The default does not include the web user interface.
@@ -25,7 +25,7 @@ in {
       storageUrl = mkOption {
         type = types.str;
         example = "https://local.appspot.com";
-        description = ''
+        description = lib.mdDoc ''
           URL used by the tablet to access the rmfakecloud service.
         '';
       };
@@ -33,7 +33,7 @@ in {
       port = mkOption {
         type = types.port;
         default = 3000;
-        description = ''
+        description = lib.mdDoc ''
           Listening port number.
         '';
       };
@@ -41,7 +41,7 @@ in {
       logLevel = mkOption {
         type = types.enum [ "info" "debug" "warn" "error" ];
         default = "info";
-        description = ''
+        description = lib.mdDoc ''
           Logging level.
         '';
       };
@@ -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.
 
@@ -63,7 +63,7 @@ in {
         type = with types; nullOr path;
         default = null;
         example = "/etc/secrets/rmfakecloud.env";
-        description = ''
+        description = lib.mdDoc ''
           Path to an environment file loaded for the rmfakecloud service.
 
           This can be used to securely store tokens and secrets outside of the
@@ -138,7 +138,7 @@ in {
         SystemCallArchitectures = "native";
         WorkingDirectory = serviceDataDir;
         StateDirectory = baseNameOf serviceDataDir;
-        UMask = 0027;
+        UMask = "0027";
       };
     };
   };
diff --git a/nixos/modules/services/misc/safeeyes.nix b/nixos/modules/services/misc/safeeyes.nix
index 638218d8bb00..9dfa2001bcb7 100644
--- a/nixos/modules/services/misc/safeeyes.nix
+++ b/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/nixos/modules/services/misc/sdrplay.nix b/nixos/modules/services/misc/sdrplay.nix
index 2801108f0828..2d5333e3885b 100644
--- a/nixos/modules/services/misc/sdrplay.nix
+++ b/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/nixos/modules/services/misc/serviio.nix b/nixos/modules/services/misc/serviio.nix
index 0ead6a816918..18e64030d79d 100644
--- a/nixos/modules/services/misc/serviio.nix
+++ b/nixos/modules/services/misc/serviio.nix
@@ -31,7 +31,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Serviio Media Server.
         '';
       };
@@ -39,7 +39,7 @@ in {
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/serviio";
-        description = ''
+        description = lib.mdDoc ''
           The directory where serviio stores its state, data, etc.
         '';
       };
@@ -80,7 +80,7 @@ in {
         23424 # mediabrowser
       ];
       allowedUDPPorts = [
-        1900 # UPnP service discovey
+        1900 # UPnP service discovery
       ];
     };
   };
diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix
index a3db99286342..bd8d8d8fa7cc 100644
--- a/nixos/modules/services/misc/sickbeard.nix
+++ b/nixos/modules/services/misc/sickbeard.nix
@@ -20,43 +20,43 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the sickbeard server.";
+        description = lib.mdDoc "Whether to enable the sickbeard server.";
       };
       package = mkOption {
         type = types.package;
         default = pkgs.sickbeard;
         defaultText = literalExpression "pkgs.sickbeard";
         example = literalExpression "pkgs.sickrage";
-        description =''
-          Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal>
+        description =lib.mdDoc ''
+          Enable `pkgs.sickrage` or `pkgs.sickgear`
           as an alternative to SickBeard
         '';
       };
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/${name}";
-        description = "Path where to store data files.";
+        description = lib.mdDoc "Path where to store data files.";
       };
       configFile = mkOption {
         type = types.path;
         default = "${cfg.dataDir}/config.ini";
         defaultText = literalExpression ''"''${config.${opt.dataDir}}/config.ini"'';
-        description = "Path to config file.";
+        description = lib.mdDoc "Path to config file.";
       };
       port = mkOption {
         type = types.ints.u16;
         default = 8081;
-        description = "Port to bind to.";
+        description = lib.mdDoc "Port to bind to.";
       };
       user = mkOption {
         type = types.str;
         default = name;
-        description = "User to run the service as";
+        description = lib.mdDoc "User to run the service as";
       };
       group = mkOption {
         type = types.str;
         default = name;
-        description = "Group to run the service as";
+        description = lib.mdDoc "Group to run the service as";
       };
     };
   };
diff --git a/nixos/modules/services/misc/signald.nix b/nixos/modules/services/misc/signald.nix
index 4cd34e4326d7..32ba154506ce 100644
--- a/nixos/modules/services/misc/signald.nix
+++ b/nixos/modules/services/misc/signald.nix
@@ -8,24 +8,24 @@ let
 in
 {
   options.services.signald = {
-    enable = mkEnableOption "the signald service";
+    enable = mkEnableOption (lib.mdDoc "the signald service");
 
     user = mkOption {
       type = types.str;
       default = defaultUser;
-      description = "User under which signald runs.";
+      description = lib.mdDoc "User under which signald runs.";
     };
 
     group = mkOption {
       type = types.str;
       default = defaultUser;
-      description = "Group under which signald runs.";
+      description = lib.mdDoc "Group under which signald runs.";
     };
 
     socketPath = mkOption {
       type = types.str;
       default = "/run/signald/signald.sock";
-      description = "Path to the signald socket";
+      description = lib.mdDoc "Path to the signald socket";
     };
   };
 
diff --git a/nixos/modules/services/misc/siproxd.nix b/nixos/modules/services/misc/siproxd.nix
index 20fe0793b84b..f1a1ed4d29b3 100644
--- a/nixos/modules/services/misc/siproxd.nix
+++ b/nixos/modules/services/misc/siproxd.nix
@@ -37,7 +37,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Siproxd SIP
           proxy/masquerading daemon.
         '';
@@ -46,20 +46,20 @@ in
       ifInbound = mkOption {
         type = types.str;
         example = "eth0";
-        description = "Local network interface";
+        description = lib.mdDoc "Local network interface";
       };
 
       ifOutbound = mkOption {
         type = types.str;
         example = "ppp0";
-        description = "Public network interface";
+        description = lib.mdDoc "Public network interface";
       };
 
       hostsAllowReg = mkOption {
         type = types.listOf types.str;
         default = [ ];
         example = [ "192.168.1.0/24" "192.168.2.0/24" ];
-        description = ''
+        description = lib.mdDoc ''
           Acess control list for incoming SIP registrations.
         '';
       };
@@ -68,7 +68,7 @@ in
         type = types.listOf types.str;
         default = [ ];
         example = [ "123.45.0.0/16" "123.46.0.0/16" ];
-        description = ''
+        description = lib.mdDoc ''
           Acess control list for incoming SIP traffic.
         '';
       };
@@ -77,7 +77,7 @@ in
         type = types.listOf types.str;
         default = [ ];
         example = [ "10.0.0.0/8" "11.0.0.0/8" ];
-        description = ''
+        description = lib.mdDoc ''
           Acess control list for denying incoming
           SIP registrations and traffic.
         '';
@@ -86,7 +86,7 @@ in
       sipListenPort = mkOption {
         type = types.int;
         default = 5060;
-        description = ''
+        description = lib.mdDoc ''
           Port to listen for incoming SIP messages.
         '';
       };
@@ -94,7 +94,7 @@ in
       rtpPortLow = mkOption {
         type = types.int;
         default = 7070;
-        description = ''
+        description = lib.mdDoc ''
          Bottom of UDP port range for incoming and outgoing RTP traffic
         '';
       };
@@ -102,7 +102,7 @@ in
       rtpPortHigh = mkOption {
         type = types.int;
         default = 7089;
-        description = ''
+        description = lib.mdDoc ''
          Top of UDP port range for incoming and outgoing RTP traffic
         '';
       };
@@ -110,7 +110,7 @@ in
       rtpTimeout = mkOption {
         type = types.int;
         default = 300;
-        description = ''
+        description = lib.mdDoc ''
           Timeout for an RTP stream. If for the specified
           number of seconds no data is relayed on an active
           stream, it is considered dead and will be killed.
@@ -120,7 +120,7 @@ in
       rtpDscp = mkOption {
         type = types.int;
         default = 46;
-        description = ''
+        description = lib.mdDoc ''
           DSCP (differentiated services) value to be assigned
           to RTP packets. Allows QOS aware routers to handle
           different types traffic with different priorities.
@@ -130,7 +130,7 @@ in
       sipDscp = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           DSCP (differentiated services) value to be assigned
           to SIP packets. Allows QOS aware routers to handle
           different types traffic with different priorities.
@@ -140,7 +140,7 @@ in
       passwordFile = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Path to per-user password file.
         '';
       };
@@ -148,7 +148,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add to siproxd configuration.
         '';
       };
diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix
index 3c3f6c4d641b..cfdfa2830ce9 100644
--- a/nixos/modules/services/misc/snapper.nix
+++ b/nixos/modules/services/misc/snapper.nix
@@ -12,7 +12,7 @@ in
     snapshotRootOnBoot = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to snapshot root on boot
       '';
     };
@@ -20,31 +20,29 @@ in
     snapshotInterval = mkOption {
       type = types.str;
       default = "hourly";
-      description = ''
+      description = lib.mdDoc ''
         Snapshot interval.
 
         The format is described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
+        {manpage}`systemd.time(7)`.
       '';
     };
 
     cleanupInterval = mkOption {
       type = types.str;
       default = "1d";
-      description = ''
+      description = lib.mdDoc ''
         Cleanup interval.
 
         The format is described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
+        {manpage}`systemd.time(7)`.
       '';
     };
 
     filters = mkOption {
       type = types.nullOr types.lines;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Global display difference filter. See man:snapper(8) for more details.
       '';
     };
@@ -64,7 +62,7 @@ in
         }
       '';
 
-      description = ''
+      description = lib.mdDoc ''
         Subvolume configuration
       '';
 
@@ -72,7 +70,7 @@ in
         options = {
           subvolume = mkOption {
             type = types.path;
-            description = ''
+            description = lib.mdDoc ''
               Path of the subvolume or mount point.
               This path is a subvolume and has to contain a subvolume named
               .snapshots.
@@ -83,7 +81,7 @@ in
           fstype = mkOption {
             type = types.enum [ "btrfs" ];
             default = "btrfs";
-            description = ''
+            description = lib.mdDoc ''
               Filesystem type. Only btrfs is stable and tested.
             '';
           };
@@ -91,7 +89,7 @@ in
           extraConfig = mkOption {
             type = types.lines;
             default = "";
-            description = ''
+            description = lib.mdDoc ''
               Additional configuration next to SUBVOLUME and FSTYPE.
               See man:snapper-configs(5).
             '';
diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix
index 77c7f0582d0b..65c51d9677d9 100644
--- a/nixos/modules/services/misc/sonarr.nix
+++ b/nixos/modules/services/misc/sonarr.nix
@@ -8,18 +8,18 @@ in
 {
   options = {
     services.sonarr = {
-      enable = mkEnableOption "Sonarr";
+      enable = mkEnableOption (lib.mdDoc "Sonarr");
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/sonarr/.config/NzbDrone";
-        description = "The directory where Sonarr stores its data files.";
+        description = lib.mdDoc "The directory where Sonarr stores its data files.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the Sonarr web interface
         '';
       };
@@ -27,13 +27,22 @@ in
       user = mkOption {
         type = types.str;
         default = "sonarr";
-        description = "User account under which Sonaar runs.";
+        description = lib.mdDoc "User account under which Sonaar runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "sonarr";
-        description = "Group under which Sonaar runs.";
+        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/nixos/modules/services/misc/sourcehut/builds.nix b/nixos/modules/services/misc/sourcehut/builds.nix
deleted file mode 100644
index 685a132d3507..000000000000
--- a/nixos/modules/services/misc/sourcehut/builds.nix
+++ /dev/null
@@ -1,236 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  scfg = cfg.builds;
-  rcfg = config.services.redis;
-  iniKey = "builds.sr.ht";
-
-  drv = pkgs.sourcehut.buildsrht;
-in
-{
-  options.services.sourcehut.builds = {
-    user = mkOption {
-      type = types.str;
-      default = "buildsrht";
-      description = ''
-        User for builds.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5002;
-      description = ''
-        Port on which the "builds" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "builds.sr.ht";
-      description = ''
-        PostgreSQL database name for builds.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/buildsrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/buildsrht"'';
-      description = ''
-        State path for builds.sr.ht.
-      '';
-    };
-
-    enableWorker = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Run workers for builds.sr.ht.
-      '';
-    };
-
-    images = mkOption {
-      type = types.attrsOf (types.attrsOf (types.attrsOf types.package));
-      default = { };
-      example = lib.literalExpression ''(let
-          # Pinning unstable to allow usage with flakes and limit rebuilds.
-          pkgs_unstable = builtins.fetchGit {
-              url = "https://github.com/NixOS/nixpkgs";
-              rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
-              ref = "nixos-unstable";
-          };
-          image_from_nixpkgs = pkgs_unstable: (import ("''${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
-            pkgs = (import pkgs_unstable {});
-          });
-        in
-        {
-          nixos.unstable.x86_64 = image_from_nixpkgs pkgs_unstable;
-        }
-      )'';
-      description = ''
-        Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
-      '';
-    };
-
-  };
-
-  config = with scfg; let
-    image_dirs = lib.lists.flatten (
-      lib.attrsets.mapAttrsToList
-        (distro: revs:
-          lib.attrsets.mapAttrsToList
-            (rev: archs:
-              lib.attrsets.mapAttrsToList
-                (arch: image:
-                  pkgs.runCommand "buildsrht-images" { } ''
-                    mkdir -p $out/${distro}/${rev}/${arch}
-                    ln -s ${image}/*.qcow2 $out/${distro}/${rev}/${arch}/root.img.qcow2
-                  '')
-                archs)
-            revs)
-        scfg.images);
-    image_dir_pre = pkgs.symlinkJoin {
-      name = "builds.sr.ht-worker-images-pre";
-      paths = image_dirs ++ [
-        "${pkgs.sourcehut.buildsrht}/lib/images"
-      ];
-    };
-    image_dir = pkgs.runCommand "builds.sr.ht-worker-images" { } ''
-      mkdir -p $out/images
-      cp -Lr ${image_dir_pre}/* $out/images
-    '';
-  in
-  lib.mkIf (cfg.enable && elem "builds" cfg.services) {
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          extraGroups = lib.optionals cfg.builds.enableWorker [ "docker" ];
-          description = "builds.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0755 ${user} ${user} -"
-      ] ++ (lib.optionals cfg.builds.enableWorker
-        [ "d ${statePath}/logs 0775 ${user} ${user} - -" ]
-      );
-
-      services = {
-        buildsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey
-          {
-            after = [ "postgresql.service" "network.target" ];
-            requires = [ "postgresql.service" ];
-            wantedBy = [ "multi-user.target" ];
-
-            description = "builds.sr.ht website service";
-
-            serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-
-            # Hack to bypass this hack: https://git.sr.ht/~sircmpwn/core.sr.ht/tree/master/item/srht-update-profiles#L6
-          } // { preStart = " "; };
-
-        buildsrht-worker = {
-          enable = scfg.enableWorker;
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-          partOf = [ "buildsrht.service" ];
-          description = "builds.sr.ht worker service";
-          path = [ pkgs.openssh pkgs.docker ];
-          preStart = let qemuPackage = pkgs.qemu_kvm;
-          in ''
-            if [[ "$(docker images -q qemu:latest 2> /dev/null)" == "" || "$(cat ${statePath}/docker-image-qemu 2> /dev/null || true)" != "${qemuPackage.version}" ]]; then
-              # Create and import qemu:latest image for docker
-              ${
-                pkgs.dockerTools.streamLayeredImage {
-                  name = "qemu";
-                  tag = "latest";
-                  contents = [ qemuPackage ];
-                }
-              } | docker load
-              # Mark down current package version
-              printf "%s" "${qemuPackage.version}" > ${statePath}/docker-image-qemu
-            fi
-          '';
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Group = "nginx";
-            Restart = "always";
-          };
-          serviceConfig.ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
-        };
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL builds.sr.ht is being served at (protocol://domain)
-      "builds.sr.ht".origin = mkDefault "http://builds.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "builds.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "builds.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "builds.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "builds.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # builds.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "builds.sr.ht".oauth-client-id = mkDefault null;
-      "builds.sr.ht".oauth-client-secret = mkDefault null;
-      # The redis connection used for the celery worker
-      "builds.sr.ht".redis = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/3";
-      # The shell used for ssh
-      "builds.sr.ht".shell = mkDefault "runner-shell";
-      # Register the builds.sr.ht dispatcher
-      "git.sr.ht::dispatch".${builtins.unsafeDiscardStringContext "${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys"} = mkDefault "${user}:${user}";
-
-      # Location for build logs, images, and control command
-    } // lib.attrsets.optionalAttrs scfg.enableWorker {
-      # Default worker stores logs that are accessible via this address:port
-      "builds.sr.ht::worker".name = mkDefault "127.0.0.1:5020";
-      "builds.sr.ht::worker".buildlogs = mkDefault "${scfg.statePath}/logs";
-      "builds.sr.ht::worker".images = mkDefault "${image_dir}/images";
-      "builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control";
-      "builds.sr.ht::worker".timeout = mkDefault "3m";
-    };
-
-    services.nginx.virtualHosts."logs.${cfg.originBase}" =
-      if scfg.enableWorker then {
-        listen = with builtins; let address = split ":" cfg.settings."builds.sr.ht::worker".name;
-        in [{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }];
-        locations."/logs".root = "${scfg.statePath}";
-      } else { };
-
-    services.nginx.virtualHosts."builds.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.buildsrht}/${pkgs.sourcehut.python.sitePackages}/buildsrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index 5a6d011a729a..7dd254e34920 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -47,33 +47,33 @@ let
     })));
   commonServiceSettings = srv: {
     origin = mkOption {
-      description = "URL ${srv}.sr.ht is being served at (protocol://domain)";
+      description = lib.mdDoc "URL ${srv}.sr.ht is being served at (protocol://domain)";
       type = types.str;
       default = "https://${srv}.${domain}";
       defaultText = "https://${srv}.example.com";
     };
     debug-host = mkOption {
-      description = "Address to bind the debug server to.";
+      description = lib.mdDoc "Address to bind the debug server to.";
       type = with types; nullOr str;
       default = null;
     };
     debug-port = mkOption {
-      description = "Port to bind the debug server to.";
+      description = lib.mdDoc "Port to bind the debug server to.";
       type = with types; nullOr str;
       default = null;
     };
     connection-string = mkOption {
-      description = "SQLAlchemy connection string for the database.";
+      description = lib.mdDoc "SQLAlchemy connection string for the database.";
       type = types.str;
       default = "postgresql:///localhost?user=${srv}srht&host=/run/postgresql";
     };
-    migrate-on-upgrade = mkEnableOption "automatic migrations on package upgrade" // { default = true; };
+    migrate-on-upgrade = mkEnableOption (lib.mdDoc "automatic migrations on package upgrade") // { default = true; };
     oauth-client-id = mkOption {
-      description = "${srv}.sr.ht's OAuth client id for meta.sr.ht.";
+      description = lib.mdDoc "${srv}.sr.ht's OAuth client id for meta.sr.ht.";
       type = types.str;
     };
     oauth-client-secret = mkOption {
-      description = "${srv}.sr.ht's OAuth client secret for meta.sr.ht.";
+      description = lib.mdDoc "${srv}.sr.ht's OAuth client secret for meta.sr.ht.";
       type = types.path;
       apply = s: "<" + toString s;
     };
@@ -83,12 +83,11 @@ let
   python = pkgs.sourcehut.python.withPackages (ps: with ps; [
     gunicorn
     eventlet
-    # For monitoring Celery: sudo -u listssrht celery --app listssrht.process -b redis+socket:///run/redis-sourcehut/redis.sock?virtual_host=5 flower
+    # For monitoring Celery: sudo -u listssrht celery --app listssrht.process -b redis+socket:///run/redis-sourcehut/redis.sock?virtual_host=1 flower
     flower
     # Sourcehut services
     srht
     buildsrht
-    dispatchsrht
     gitsrht
     hgsrht
     hubsrht
@@ -101,23 +100,23 @@ 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 = ''
+      description = lib.mdDoc ''
         Services that may be displayed as links in the title bar of the Web interface.
       '';
     };
@@ -125,42 +124,42 @@ in
     listenAddress = mkOption {
       type = types.str;
       default = "localhost";
-      description = "Address to bind to.";
+      description = lib.mdDoc "Address to bind to.";
     };
 
     python = mkOption {
       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 = {};
-        description = "Virtual-host configuration merged with all Sourcehut's virtual-hosts.";
+        description = lib.mdDoc "Virtual-host configuration merged with all Sourcehut's virtual-hosts.";
       };
     };
 
     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 {
@@ -168,48 +167,48 @@ in
         freeformType = settingsFormat.type;
         options."sr.ht" = {
           global-domain = mkOption {
-            description = "Global domain name.";
+            description = lib.mdDoc "Global domain name.";
             type = types.str;
             example = "example.com";
           };
           environment = mkOption {
-            description = "Values other than \"production\" adds a banner to each page.";
+            description = lib.mdDoc "Values other than \"production\" adds a banner to each page.";
             type = types.enum [ "development" "production" ];
             default = "development";
           };
           network-key = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               An absolute file path (which should be outside the Nix-store)
-              to a secret key to encrypt internal messages with. Use <code>srht-keygen network</code> to
+              to a secret key to encrypt internal messages with. Use `srht-keygen network` to
               generate this key. It must be consistent between all services and nodes.
             '';
             type = types.path;
             apply = s: "<" + toString s;
           };
           owner-email = mkOption {
-            description = "Owner's email.";
+            description = lib.mdDoc "Owner's email.";
             type = types.str;
             default = "contact@example.com";
           };
           owner-name = mkOption {
-            description = "Owner's name.";
+            description = lib.mdDoc "Owner's name.";
             type = types.str;
             default = "John Doe";
           };
           site-blurb = mkOption {
-            description = "Blurb for your site.";
+            description = lib.mdDoc "Blurb for your site.";
             type = types.str;
             default = "the hacker's forge";
           };
           site-info = mkOption {
-            description = "The top-level info page for your site.";
+            description = lib.mdDoc "The top-level info page for your site.";
             type = types.str;
             default = "https://sourcehut.org";
           };
           service-key = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               An absolute file path (which should be outside the Nix-store)
-              to a key used for encrypting session cookies. Use <code>srht-keygen service</code> to
+              to a key used for encrypting session cookies. Use `srht-keygen service` to
               generate the service key. This must be shared between each node of the same
               service (e.g. git1.sr.ht and git2.sr.ht), but different services may use
               different keys. If you configure all of your services with the same
@@ -219,12 +218,12 @@ in
             apply = s: "<" + toString s;
           };
           site-name = mkOption {
-            description = "The name of your network of sr.ht-based sites.";
+            description = lib.mdDoc "The name of your network of sr.ht-based sites.";
             type = types.str;
             default = "sourcehut";
           };
           source-url = mkOption {
-            description = "The source code for your fork of sr.ht.";
+            description = lib.mdDoc "The source code for your fork of sr.ht.";
             type = types.str;
             default = "https://git.sr.ht/~sircmpwn/srht";
           };
@@ -232,40 +231,52 @@ in
         options.mail = {
           smtp-host = mkOptionNullOrStr "Outgoing SMTP host.";
           smtp-port = mkOption {
-            description = "Outgoing SMTP port.";
+            description = lib.mdDoc "Outgoing SMTP port.";
             type = with types; nullOr port;
             default = null;
           };
           smtp-user = mkOptionNullOrStr "Outgoing SMTP user.";
           smtp-password = mkOptionNullOrStr "Outgoing SMTP password.";
-          smtp-from = mkOptionNullOrStr "Outgoing SMTP FROM.";
+          smtp-from = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Outgoing SMTP FROM.";
+          };
           error-to = mkOptionNullOrStr "Address receiving application exceptions";
           error-from = mkOptionNullOrStr "Address sending application exceptions";
-          pgp-privkey = mkOptionNullOrStr ''
-            An absolute file path (which should be outside the Nix-store)
-            to an OpenPGP private key.
+          pgp-privkey = mkOption {
+            type = types.str;
+            description = lib.mdDoc ''
+              An absolute file path (which should be outside the Nix-store)
+              to an OpenPGP private key.
 
-            Your PGP key information (DO NOT mix up pub and priv here)
-            You must remove the password from your secret key, if present.
-            You can do this with <code>gpg --edit-key [key-id]</code>,
-            then use the <code>passwd</code> command and do not enter a new password.
-          '';
-          pgp-pubkey = mkOptionNullOrStr "OpenPGP public key.";
-          pgp-key-id = mkOptionNullOrStr "OpenPGP key identifier.";
+              Your PGP key information (DO NOT mix up pub and priv here)
+              You must remove the password from your secret key, if present.
+              You can do this with `gpg --edit-key [key-id]`,
+              then use the `passwd` command and do not enter a new password.
+            '';
+          };
+          pgp-pubkey = mkOption {
+            type = with types; either path str;
+            description = lib.mdDoc "OpenPGP public key.";
+          };
+          pgp-key-id = mkOption {
+            type = types.str;
+            description = lib.mdDoc "OpenPGP key identifier.";
+          };
         };
         options.objects = {
           s3-upstream = mkOption {
-            description = "Configure the S3-compatible object storage service.";
+            description = lib.mdDoc "Configure the S3-compatible object storage service.";
             type = with types; nullOr str;
             default = null;
           };
           s3-access-key = mkOption {
-            description = "Access key to the S3-compatible object storage service";
+            description = lib.mdDoc "Access key to the S3-compatible object storage service";
             type = with types; nullOr str;
             default = null;
           };
           s3-secret-key = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               An absolute file path (which should be outside the Nix-store)
               to the secret key of the S3-compatible object storage service.
             '';
@@ -276,59 +287,33 @@ in
         };
         options.webhooks = {
           private-key = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               An absolute file path (which should be outside the Nix-store)
               to a base64-encoded Ed25519 key for signing webhook payloads.
               This should be consistent for all *.sr.ht sites,
               as this key will be used to verify signatures
               from other sites in your network.
-              Use the <code>srht-keygen webhook</code> command to generate a key.
+              Use the `srht-keygen webhook` command to generate a key.
             '';
             type = types.path;
             apply = s: "<" + toString s;
           };
         };
 
-        options."dispatch.sr.ht" = commonServiceSettings "dispatch" // {
-        };
-        options."dispatch.sr.ht::github" = {
-          oauth-client-id = mkOptionNullOrStr "OAuth client id.";
-          oauth-client-secret = mkOptionNullOrStr "OAuth client secret.";
-        };
-        options."dispatch.sr.ht::gitlab" = {
-          enabled = mkEnableOption "GitLab integration";
-          canonical-upstream = mkOption {
-            type = types.str;
-            description = "Canonical upstream.";
-            default = "gitlab.com";
-          };
-          repo-cache = mkOption {
-            type = types.str;
-            description = "Repository cache directory.";
-            default = "./repo-cache";
-          };
-          "gitlab.com" = mkOption {
-            type = with types; nullOr str;
-            description = "GitLab id and secret.";
-            default = null;
-            example = "GitLab:application id:secret";
-          };
-        };
-
         options."builds.sr.ht" = commonServiceSettings "builds" // {
-          allow-free = mkEnableOption "nonpaying users to submit builds";
+          allow-free = mkEnableOption (lib.mdDoc "nonpaying users to submit builds");
           redis = mkOption {
-            description = "The Redis connection used for the Celery worker.";
+            description = lib.mdDoc "The Redis connection used for the Celery worker.";
             type = types.str;
             default = "redis+socket:///run/redis-sourcehut-buildsrht/redis.sock?virtual_host=2";
           };
           shell = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Scripts used to launch on SSH connection.
-              <literal>/usr/bin/master-shell</literal> on master,
-              <literal>/usr/bin/runner-shell</literal> on runner.
+              `/usr/bin/master-shell` on master,
+              `/usr/bin/runner-shell` on runner.
               If master and worker are on the same system
-              set to <literal>/usr/bin/runner-shell</literal>.
+              set to `/usr/bin/runner-shell`.
             '';
             type = types.enum ["/usr/bin/master-shell" "/usr/bin/runner-shell"];
             default = "/usr/bin/master-shell";
@@ -336,19 +321,19 @@ in
         };
         options."builds.sr.ht::worker" = {
           bind-address = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               HTTP bind address for serving local build information/monitoring.
             '';
             type = types.str;
             default = "localhost:8080";
           };
           buildlogs = mkOption {
-            description = "Path to write build logs.";
+            description = lib.mdDoc "Path to write build logs.";
             type = types.str;
             default = "/var/log/sourcehut/buildsrht-worker";
           };
           name = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Listening address and listening port
               of the build runner (with HTTP port if not 80).
             '';
@@ -356,9 +341,9 @@ in
             default = "localhost:5020";
           };
           timeout = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Max build duration.
-              See <link xlink:href="https://golang.org/pkg/time/#ParseDuration"/>.
+              See <https://golang.org/pkg/time/#ParseDuration>.
             '';
             type = types.str;
             default = "3m";
@@ -367,12 +352,12 @@ in
 
         options."git.sr.ht" = commonServiceSettings "git" // {
           outgoing-domain = mkOption {
-            description = "Outgoing domain.";
+            description = lib.mdDoc "Outgoing domain.";
             type = types.str;
             default = "https://git.localhost.localdomain";
           };
           post-update-script = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               A post-update script which is installed in every git repo.
               This setting is propagated to newer and existing repositories.
             '';
@@ -381,7 +366,7 @@ in
             defaultText = "\${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
           };
           repos = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Path to git repositories on disk.
               If changing the default, you must ensure that
               the gitsrht's user as read and write access to it.
@@ -390,18 +375,18 @@ in
             default = "/var/lib/sourcehut/gitsrht/repos";
           };
           webhooks = mkOption {
-            description = "The Redis connection used for the webhooks worker.";
+            description = lib.mdDoc "The Redis connection used for the webhooks worker.";
             type = types.str;
             default = "redis+socket:///run/redis-sourcehut-gitsrht/redis.sock?virtual_host=1";
           };
         };
         options."git.sr.ht::api" = {
           internal-ipnet = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Set of IP subnets which are permitted to utilize internal API
               authentication. This should be limited to the subnets
               from which your *.sr.ht services are running.
-              See <xref linkend="opt-services.sourcehut.listenAddress"/>.
+              See [](#opt-services.sourcehut.listenAddress).
             '';
             type = with types; listOf str;
             default = [ "127.0.0.0/8" "::1/128" ];
@@ -410,7 +395,7 @@ in
 
         options."hg.sr.ht" = commonServiceSettings "hg" // {
           changegroup-script = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               A changegroup script which is installed in every mercurial repo.
               This setting is propagated to newer and existing repositories.
             '';
@@ -419,7 +404,7 @@ in
             defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
           };
           repos = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Path to mercurial repositories on disk.
               If changing the default, you must ensure that
               the hgsrht's user as read and write access to it.
@@ -432,18 +417,18 @@ in
             (defaults to where the hgsrht code is)
           '';
           clone_bundle_threshold = mkOption {
-            description = ".hg/store size (in MB) past which the nightly job generates clone bundles.";
+            description = lib.mdDoc ".hg/store size (in MB) past which the nightly job generates clone bundles.";
             type = types.ints.unsigned;
             default = 50;
           };
           hg_ssh = mkOption {
-            description = "Path to hg-ssh (if not in $PATH).";
+            description = lib.mdDoc "Path to hg-ssh (if not in $PATH).";
             type = types.str;
             default = "${pkgs.mercurial}/bin/hg-ssh";
             defaultText = "\${pkgs.mercurial}/bin/hg-ssh";
           };
           webhooks = mkOption {
-            description = "The Redis connection used for the webhooks worker.";
+            description = lib.mdDoc "The Redis connection used for the webhooks worker.";
             type = types.str;
             default = "redis+socket:///run/redis-sourcehut-hgsrht/redis.sock?virtual_host=1";
           };
@@ -453,31 +438,31 @@ 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 = "Outgoing email for notifications generated by users.";
+            description = lib.mdDoc "Outgoing email for notifications generated by users.";
             type = types.str;
             default = "lists-notify@localhost.localdomain";
           };
           posting-domain = mkOption {
-            description = "Posting domain.";
+            description = lib.mdDoc "Posting domain.";
             type = types.str;
             default = "lists.localhost.localdomain";
           };
           redis = mkOption {
-            description = "The Redis connection used for the Celery worker.";
+            description = lib.mdDoc "The Redis connection used for the Celery worker.";
             type = types.str;
             default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=2";
           };
           webhooks = mkOption {
-            description = "The Redis connection used for the webhooks worker.";
+            description = lib.mdDoc "The Redis connection used for the webhooks worker.";
             type = types.str;
             default = "redis+socket:///run/redis-sourcehut-listssrht/redis.sock?virtual_host=1";
           };
         };
         options."lists.sr.ht::worker" = {
           reject-mimetypes = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Comma-delimited list of Content-Types to reject. Messages with Content-Types
               included in this list are rejected. Multipart messages are always supported,
               and each part is checked against this list.
@@ -488,12 +473,12 @@ in
             default = ["text/html"];
           };
           reject-url = mkOption {
-            description = "Reject URL.";
+            description = lib.mdDoc "Reject URL.";
             type = types.str;
             default = "https://man.sr.ht/lists.sr.ht/etiquette.md";
           };
           sock = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
               Alternatively, specify IP:PORT and an SMTP server will be run instead.
             '';
@@ -501,7 +486,7 @@ in
             default = "/tmp/lists.sr.ht-lmtp.sock";
           };
           sock-group = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               The lmtp daemon will make the unix socket group-read/write
               for users in this group.
             '';
@@ -517,38 +502,38 @@ in
           removeAttrs (commonServiceSettings "meta")
             ["oauth-client-id" "oauth-client-secret"] // {
           api-origin = mkOption {
-            description = "Origin URL for API, 100 more than web.";
+            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 = "The Redis connection used for the webhooks worker.";
+            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 {
-            description = ''
+            description = lib.mdDoc ''
               Set of IP subnets which are permitted to utilize internal API
               authentication. This should be limited to the subnets
               from which your *.sr.ht services are running.
-              See <xref linkend="opt-services.sourcehut.listenAddress"/>.
+              See [](#opt-services.sourcehut.listenAddress).
             '';
             type = with types; listOf str;
             default = [ "127.0.0.0/8" "::1/128" ];
           };
         };
         options."meta.sr.ht::aliases" = mkOption {
-          description = "Aliases for the client IDs of commonly used OAuth clients.";
+          description = lib.mdDoc "Aliases for the client IDs of commonly used OAuth clients.";
           type = with types; attrsOf int;
           default = {};
           example = { "git.sr.ht" = 12345; };
         };
         options."meta.sr.ht::billing" = {
-          enabled = mkEnableOption "the billing system";
+          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)
@@ -558,14 +543,14 @@ in
           };
         };
         options."meta.sr.ht::settings" = {
-          registration = mkEnableOption "public registration";
+          registration = mkEnableOption (lib.mdDoc "public registration");
           onboarding-redirect = mkOption {
-            description = "Where to redirect new users upon registration.";
+            description = lib.mdDoc "Where to redirect new users upon registration.";
             type = types.str;
             default = "https://meta.localhost.localdomain";
           };
           user-invites = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               How many invites each user is issued upon registration
               (only applicable if open registration is disabled).
             '';
@@ -576,7 +561,7 @@ in
 
         options."pages.sr.ht" = commonServiceSettings "pages" // {
           gemini-certs = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               An absolute file path (which should be outside the Nix-store)
               to Gemini certificates.
             '';
@@ -584,14 +569,14 @@ in
             default = null;
           };
           max-site-size = mkOption {
-            description = "Maximum size of any given site (post-gunzip), in MiB.";
+            description = lib.mdDoc "Maximum size of any given site (post-gunzip), in MiB.";
             type = types.int;
             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;
@@ -599,11 +584,11 @@ in
         };
         options."pages.sr.ht::api" = {
           internal-ipnet = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Set of IP subnets which are permitted to utilize internal API
               authentication. This should be limited to the subnets
               from which your *.sr.ht services are running.
-              See <xref linkend="opt-services.sourcehut.listenAddress"/>.
+              See [](#opt-services.sourcehut.listenAddress).
             '';
             type = with types; listOf str;
             default = [ "127.0.0.0/8" "::1/128" ];
@@ -615,24 +600,24 @@ in
 
         options."todo.sr.ht" = commonServiceSettings "todo" // {
           notify-from = mkOption {
-            description = "Outgoing email for notifications generated by users.";
+            description = lib.mdDoc "Outgoing email for notifications generated by users.";
             type = types.str;
             default = "todo-notify@localhost.localdomain";
           };
           webhooks = mkOption {
-            description = "The Redis connection used for the webhooks worker.";
+            description = lib.mdDoc "The Redis connection used for the webhooks worker.";
             type = types.str;
             default = "redis+socket:///run/redis-sourcehut-todosrht/redis.sock?virtual_host=1";
           };
         };
         options."todo.sr.ht::mail" = {
           posting-domain = mkOption {
-            description = "Posting domain.";
+            description = lib.mdDoc "Posting domain.";
             type = types.str;
             default = "todo.localhost.localdomain";
           };
           sock = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
               Alternatively, specify IP:PORT and an SMTP server will be run instead.
             '';
@@ -640,7 +625,7 @@ in
             default = "/tmp/todo.sr.ht-lmtp.sock";
           };
           sock-group = mkOption {
-            description = ''
+            description = lib.mdDoc ''
               The lmtp daemon will make the unix socket group-read/write
               for users in this group.
             '';
@@ -650,23 +635,23 @@ in
         };
       };
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         The configuration for the sourcehut network.
       '';
     };
 
     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));
@@ -686,7 +671,7 @@ in
             nixos.unstable.x86_64 = image_from_nixpkgs;
           }
         )'';
-        description = ''
+        description = lib.mdDoc ''
           Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
         '';
       };
@@ -698,12 +683,12 @@ in
         default = pkgs.git;
         defaultText = literalExpression "pkgs.git";
         example = literalExpression "pkgs.gitFull";
-        description = ''
+        description = lib.mdDoc ''
           Git package for git.sr.ht. This can help silence collisions.
         '';
       };
       fcgiwrap.preforkProcess = mkOption {
-        description = "Number of fcgiwrap processes to prefork.";
+        description = lib.mdDoc "Number of fcgiwrap processes to prefork.";
         type = types.int;
         default = 4;
       };
@@ -714,14 +699,14 @@ in
         type = types.package;
         default = pkgs.mercurial;
         defaultText = literalExpression "pkgs.mercurial";
-        description = ''
+        description = lib.mdDoc ''
           Mercurial package for hg.sr.ht. This can help silence collisions.
         '';
       };
       cloneBundles = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories).
         '';
       };
@@ -732,12 +717,12 @@ in
         extraArgs = mkOption {
           type = with types; listOf str;
           default = [ "--loglevel DEBUG" "--pool eventlet" "--without-heartbeat" ];
-          description = "Extra arguments passed to the Celery responsible for processing mails.";
+          description = lib.mdDoc "Extra arguments passed to the Celery responsible for processing mails.";
         };
         celeryConfig = mkOption {
           type = types.lines;
           default = "";
-          description = "Content of the <literal>celeryconfig.py</literal> used by the Celery of <literal>listssrht-process</literal>.";
+          description = lib.mdDoc "Content of the `celeryconfig.py` used by the Celery of `listssrht-process`.";
         };
       };
     };
@@ -905,6 +890,11 @@ in
       inherit configIniOfService;
       srvsrht = "buildsrht";
       port = 5002;
+      extraServices.buildsrht-api = {
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "5s";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.buildsrht}/bin/buildsrht-api -b ${cfg.listenAddress}:${toString (cfg.builds.port + 100)}";
+      };
       # TODO: a celery worker on the master and worker are apparently needed
       extraServices.buildsrht-worker = let
         qemuPackage = pkgs.qemu_kvm;
@@ -928,13 +918,13 @@ in
           fi
         '';
         serviceConfig = {
-          ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
+          ExecStart = "${pkgs.sourcehut.buildsrht}/bin/buildsrht-worker";
           BindPaths = [ cfg.settings."builds.sr.ht::worker".buildlogs ];
           LogsDirectory = [ "sourcehut/${serviceName}" ];
           RuntimeDirectory = [ "sourcehut/${serviceName}/subdir" ];
           StateDirectory = [ "sourcehut/${serviceName}" ];
           TimeoutStartSec = "1800s";
-          # builds.sr.ht-worker looks up ../config.ini
+          # buildsrht-worker looks up ../config.ini
           WorkingDirectory = "-"+"/run/sourcehut/${serviceName}/subdir";
         };
       };
@@ -952,12 +942,12 @@ in
           ) cfg.builds.images
         );
         image_dir_pre = pkgs.symlinkJoin {
-          name = "builds.sr.ht-worker-images-pre";
+          name = "buildsrht-worker-images-pre";
           paths = image_dirs;
             # FIXME: not working, apparently because ubuntu/latest is a broken link
             # ++ [ "${pkgs.sourcehut.buildsrht}/lib/images" ];
         };
-        image_dir = pkgs.runCommand "builds.sr.ht-worker-images" { } ''
+        image_dir = pkgs.runCommand "buildsrht-worker-images" { } ''
           mkdir -p $out/images
           cp -Lr ${image_dir_pre}/* $out/images
         '';
@@ -1004,11 +994,6 @@ in
       ];
     })
 
-    (import ./service.nix "dispatch" {
-      inherit configIniOfService;
-      port = 5005;
-    })
-
     (import ./service.nix "git" (let
       baseService = {
         path = [ cfg.git.package ];
@@ -1081,6 +1066,11 @@ in
           };
         })
       ];
+      extraServices.gitsrht-api = {
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "5s";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-api -b ${cfg.listenAddress}:${toString (cfg.git.port + 100)}";
+      };
       extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
         serviceConfig = {
           # Socket is passed by gitsrht-fcgiwrap.socket
@@ -1124,6 +1114,11 @@ in
         timerConfig.OnCalendar = ["daily"];
         timerConfig.AccuracySec = "1h";
       };
+      extraServices.hgsrht-api = {
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "5s";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.hgsrht}/bin/hgsrht-api -b ${cfg.listenAddress}:${toString (cfg.hg.port + 100)}";
+      };
       extraConfig = mkMerge [
         {
           users.users.${cfg.hg.user}.shell = pkgs.bash;
@@ -1184,6 +1179,11 @@ in
       inherit configIniOfService;
       port = 5006;
       webhooks = true;
+      extraServices.listssrht-api = {
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "5s";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.listssrht}/bin/listssrht-api -b ${cfg.listenAddress}:${toString (cfg.lists.port + 100)}";
+      };
       # Receive the mail from Postfix and enqueue them into Redis and PostgreSQL
       extraServices.listssrht-lmtp = {
         wants = [ "postfix.service" ];
@@ -1232,9 +1232,13 @@ in
       inherit configIniOfService;
       port = 5000;
       webhooks = true;
+      extraTimers.metasrht-daily.timerConfig = {
+        OnCalendar = ["daily"];
+        AccuracySec = "1h";
+      };
       extraServices.metasrht-api = {
         serviceConfig.Restart = "always";
-        serviceConfig.RestartSec = "2s";
+        serviceConfig.RestartSec = "5s";
         preStart = "set -x\n" + concatStringsSep "\n\n" (attrValues (mapAttrs (k: s:
           let srvMatch = builtins.match "^([a-z]*)\\.sr\\.ht$" k;
               srv = head srvMatch;
@@ -1248,10 +1252,6 @@ in
           ) cfg.settings));
         serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
       };
-      extraTimers.metasrht-daily.timerConfig = {
-        OnCalendar = ["daily"];
-        AccuracySec = "1h";
-      };
       extraConfig = mkMerge [
         {
           assertions = [
@@ -1348,6 +1348,11 @@ in
       inherit configIniOfService;
       port = 5003;
       webhooks = true;
+      extraServices.todosrht-api = {
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "5s";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.todosrht}/bin/todosrht-api -b ${cfg.listenAddress}:${toString (cfg.todo.port + 100)}";
+      };
       extraServices.todosrht-lmtp = {
         wants = [ "postfix.service" ];
         unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
@@ -1379,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.maintainers = with maintainers; [ tomberek ];
 }
diff --git a/nixos/modules/services/misc/sourcehut/dispatch.nix b/nixos/modules/services/misc/sourcehut/dispatch.nix
deleted file mode 100644
index 292a51d3e1c5..000000000000
--- a/nixos/modules/services/misc/sourcehut/dispatch.nix
+++ /dev/null
@@ -1,127 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  cfgIni = cfg.settings;
-  scfg = cfg.dispatch;
-  iniKey = "dispatch.sr.ht";
-
-  drv = pkgs.sourcehut.dispatchsrht;
-in
-{
-  options.services.sourcehut.dispatch = {
-    user = mkOption {
-      type = types.str;
-      default = "dispatchsrht";
-      description = ''
-        User for dispatch.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5005;
-      description = ''
-        Port on which the "dispatch" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "dispatch.sr.ht";
-      description = ''
-        PostgreSQL database name for dispatch.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/dispatchsrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/dispatchsrht"'';
-      description = ''
-        State path for dispatch.sr.ht.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "dispatch" cfg.services) {
-
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          description = "dispatch.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0750 ${user} ${user} -"
-      ];
-
-      services.dispatchsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-        after = [ "postgresql.service" "network.target" ];
-        requires = [ "postgresql.service" ];
-        wantedBy = [ "multi-user.target" ];
-
-        description = "dispatch.sr.ht website service";
-
-        serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL dispatch.sr.ht is being served at (protocol://domain)
-      "dispatch.sr.ht".origin = mkDefault "http://dispatch.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "dispatch.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "dispatch.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "dispatch.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "dispatch.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # dispatch.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "dispatch.sr.ht".oauth-client-id = mkDefault null;
-      "dispatch.sr.ht".oauth-client-secret = mkDefault null;
-
-      # Github Integration
-      "dispatch.sr.ht::github".oauth-client-id = mkDefault null;
-      "dispatch.sr.ht::github".oauth-client-secret = mkDefault null;
-
-      # Gitlab Integration
-      "dispatch.sr.ht::gitlab".enabled = mkDefault null;
-      "dispatch.sr.ht::gitlab".canonical-upstream = mkDefault "gitlab.com";
-      "dispatch.sr.ht::gitlab".repo-cache = mkDefault "./repo-cache";
-      # "dispatch.sr.ht::gitlab"."gitlab.com" = mkDefault "GitLab:application id:secret";
-    };
-
-    services.nginx.virtualHosts."dispatch.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.dispatchsrht}/${pkgs.sourcehut.python.sitePackages}/dispatchsrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/git.nix b/nixos/modules/services/misc/sourcehut/git.nix
deleted file mode 100644
index ff110905d184..000000000000
--- a/nixos/modules/services/misc/sourcehut/git.nix
+++ /dev/null
@@ -1,217 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  scfg = cfg.git;
-  iniKey = "git.sr.ht";
-
-  rcfg = config.services.redis;
-  drv = pkgs.sourcehut.gitsrht;
-in
-{
-  options.services.sourcehut.git = {
-    user = mkOption {
-      type = types.str;
-      visible = false;
-      internal = true;
-      readOnly = true;
-      default = "git";
-      description = ''
-        User for git.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5001;
-      description = ''
-        Port on which the "git" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "git.sr.ht";
-      description = ''
-        PostgreSQL database name for git.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/gitsrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/gitsrht"'';
-      description = ''
-        State path for git.sr.ht.
-      '';
-    };
-
-    package = mkOption {
-      type = types.package;
-      default = pkgs.git;
-      defaultText = literalExpression "pkgs.git";
-      example = literalExpression "pkgs.gitFull";
-      description = ''
-        Git package for git.sr.ht. This can help silence collisions.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "git" cfg.services) {
-    # sshd refuses to run with `Unsafe AuthorizedKeysCommand ... bad ownership or modes for directory /nix/store`
-    environment.etc."ssh/gitsrht-dispatch" = {
-      mode = "0755";
-      text = ''
-        #! ${pkgs.stdenv.shell}
-        ${cfg.python}/bin/gitsrht-dispatch "$@"
-      '';
-    };
-
-    # Needs this in the $PATH when sshing into the server
-    environment.systemPackages = [ cfg.git.package ];
-
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          # https://stackoverflow.com/questions/22314298/git-push-results-in-fatal-protocol-error-bad-line-length-character-this
-          # Probably could use gitsrht-shell if output is restricted to just parameters...
-          shell = pkgs.bash;
-          description = "git.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services = {
-      cron.systemCronJobs = [ "*/20 * * * * ${cfg.python}/bin/gitsrht-periodic" ];
-      fcgiwrap.enable = true;
-
-      openssh.authorizedKeysCommand = ''/etc/ssh/gitsrht-dispatch "%u" "%h" "%t" "%k"'';
-      openssh.authorizedKeysCommandUser = "root";
-      openssh.extraConfig = ''
-        PermitUserEnvironment SRHT_*
-      '';
-
-      postgresql = {
-        authentication = ''
-          local ${database} ${user} trust
-        '';
-        ensureDatabases = [ database ];
-        ensureUsers = [
-          {
-            name = user;
-            ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-          }
-        ];
-      };
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        # /var/log is owned by root
-        "f /var/log/git-srht-shell 0644 ${user} ${user} -"
-
-        "d ${statePath} 0750 ${user} ${user} -"
-        "d ${cfg.settings."${iniKey}".repos} 2755 ${user} ${user} -"
-      ];
-
-      services = {
-        gitsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-          after = [ "redis.service" "postgresql.service" "network.target" ];
-          requires = [ "redis.service" "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          # Needs internally to create repos at the very least
-          path = [ pkgs.git ];
-          description = "git.sr.ht website service";
-
-          serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-        };
-
-        gitsrht-webhooks = {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "git.sr.ht webhooks service";
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Restart = "always";
-          };
-
-          serviceConfig.ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
-        };
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL git.sr.ht is being served at (protocol://domain)
-      "git.sr.ht".origin = mkDefault "http://git.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "git.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "git.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "git.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "git.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # The redis connection used for the webhooks worker
-      "git.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1";
-
-      # A post-update script which is installed in every git repo.
-      "git.sr.ht".post-update-script = mkDefault "${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook";
-
-      # git.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "git.sr.ht".oauth-client-id = mkDefault null;
-      "git.sr.ht".oauth-client-secret = mkDefault null;
-      # Path to git repositories on disk
-      "git.sr.ht".repos = mkDefault "/var/lib/git";
-
-      "git.sr.ht".outgoing-domain = mkDefault "http://git.${cfg.originBase}";
-
-      # The authorized keys hook uses this to dispatch to various handlers
-      # The format is a program to exec into as the key, and the user to match as the
-      # value. When someone tries to log in as this user, this program is executed
-      # and is expected to omit an AuthorizedKeys file.
-      #
-      # Discard of the string context is in order to allow derivation-derived strings.
-      # This is safe if the relevant package is installed which will be the case if the setting is utilized.
-      "git.sr.ht::dispatch".${builtins.unsafeDiscardStringContext "${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys"} = mkDefault "${user}:${user}";
-    };
-
-    services.nginx.virtualHosts."git.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.gitsrht}/${pkgs.sourcehut.python.sitePackages}/gitsrht";
-      extraConfig = ''
-            location = /authorize {
-            proxy_pass http://${cfg.address}:${toString port};
-            proxy_pass_request_body off;
-            proxy_set_header Content-Length "";
-            proxy_set_header X-Original-URI $request_uri;
-        }
-            location ~ ^/([^/]+)/([^/]+)/(HEAD|info/refs|objects/info/.*|git-upload-pack).*$ {
-                auth_request /authorize;
-                root /var/lib/git;
-                fastcgi_pass unix:/run/fcgiwrap.sock;
-                fastcgi_param SCRIPT_FILENAME ${pkgs.git}/bin/git-http-backend;
-                fastcgi_param PATH_INFO $uri;
-                fastcgi_param GIT_PROJECT_ROOT $document_root;
-                fastcgi_read_timeout 500s;
-                include ${config.services.nginx.package}/conf/fastcgi_params;
-                gzip off;
-            }
-      '';
-
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/hg.nix b/nixos/modules/services/misc/sourcehut/hg.nix
deleted file mode 100644
index 6ba1df8b6ddb..000000000000
--- a/nixos/modules/services/misc/sourcehut/hg.nix
+++ /dev/null
@@ -1,175 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  scfg = cfg.hg;
-  iniKey = "hg.sr.ht";
-
-  rcfg = config.services.redis;
-  drv = pkgs.sourcehut.hgsrht;
-in
-{
-  options.services.sourcehut.hg = {
-    user = mkOption {
-      type = types.str;
-      internal = true;
-      readOnly = true;
-      default = "hg";
-      description = ''
-        User for hg.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5010;
-      description = ''
-        Port on which the "hg" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "hg.sr.ht";
-      description = ''
-        PostgreSQL database name for hg.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/hgsrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/hgsrht"'';
-      description = ''
-        State path for hg.sr.ht.
-      '';
-    };
-
-    cloneBundles = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Generate clonebundles (which require more disk space but dramatically speed up cloning large repositories).
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "hg" cfg.services) {
-    # In case it ever comes into being
-    environment.etc."ssh/hgsrht-dispatch" = {
-      mode = "0755";
-      text = ''
-        #! ${pkgs.stdenv.shell}
-        ${cfg.python}/bin/gitsrht-dispatch $@
-      '';
-    };
-
-    environment.systemPackages = [ pkgs.mercurial ];
-
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          # Assuming hg.sr.ht needs this too
-          shell = pkgs.bash;
-          description = "hg.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services = {
-      cron.systemCronJobs = [ "*/20 * * * * ${cfg.python}/bin/hgsrht-periodic" ]
-        ++ optional cloneBundles "0 * * * * ${cfg.python}/bin/hgsrht-clonebundles";
-
-      openssh.authorizedKeysCommand = ''/etc/ssh/hgsrht-dispatch "%u" "%h" "%t" "%k"'';
-      openssh.authorizedKeysCommandUser = "root";
-      openssh.extraConfig = ''
-        PermitUserEnvironment SRHT_*
-      '';
-
-      postgresql = {
-        authentication = ''
-          local ${database} ${user} trust
-        '';
-        ensureDatabases = [ database ];
-        ensureUsers = [
-          {
-            name = user;
-            ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-          }
-        ];
-      };
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        # /var/log is owned by root
-        "f /var/log/hg-srht-shell 0644 ${user} ${user} -"
-
-        "d ${statePath} 0750 ${user} ${user} -"
-        "d ${cfg.settings."${iniKey}".repos} 2755 ${user} ${user} -"
-      ];
-
-      services.hgsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-        after = [ "redis.service" "postgresql.service" "network.target" ];
-        requires = [ "redis.service" "postgresql.service" ];
-        wantedBy = [ "multi-user.target" ];
-
-        path = [ pkgs.mercurial ];
-        description = "hg.sr.ht website service";
-
-        serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL hg.sr.ht is being served at (protocol://domain)
-      "hg.sr.ht".origin = mkDefault "http://hg.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "hg.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "hg.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "hg.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # The redis connection used for the webhooks worker
-      "hg.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1";
-      # A post-update script which is installed in every mercurial repo.
-      "hg.sr.ht".changegroup-script = mkDefault "${cfg.python}/bin/hgsrht-hook-changegroup";
-      # hg.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "hg.sr.ht".oauth-client-id = mkDefault null;
-      "hg.sr.ht".oauth-client-secret = mkDefault null;
-      # Path to mercurial repositories on disk
-      "hg.sr.ht".repos = mkDefault "/var/lib/hg";
-      # Path to the srht mercurial extension
-      # (defaults to where the hgsrht code is)
-      # "hg.sr.ht".srhtext = mkDefault null;
-      # .hg/store size (in MB) past which the nightly job generates clone bundles.
-      # "hg.sr.ht".clone_bundle_threshold = mkDefault 50;
-      # Path to hg-ssh (if not in $PATH)
-      # "hg.sr.ht".hg_ssh = mkDefault /path/to/hg-ssh;
-
-      # The authorized keys hook uses this to dispatch to various handlers
-      # The format is a program to exec into as the key, and the user to match as the
-      # value. When someone tries to log in as this user, this program is executed
-      # and is expected to omit an AuthorizedKeys file.
-      #
-      # Uncomment the relevant lines to enable the various sr.ht dispatchers.
-      "hg.sr.ht::dispatch"."/run/current-system/sw/bin/hgsrht-keys" = mkDefault "${user}:${user}";
-    };
-
-    # TODO: requires testing and addition of hg-specific requirements
-    services.nginx.virtualHosts."hg.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.hgsrht}/${pkgs.sourcehut.python.sitePackages}/hgsrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/hub.nix b/nixos/modules/services/misc/sourcehut/hub.nix
deleted file mode 100644
index 7d137a765056..000000000000
--- a/nixos/modules/services/misc/sourcehut/hub.nix
+++ /dev/null
@@ -1,120 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  cfgIni = cfg.settings;
-  scfg = cfg.hub;
-  iniKey = "hub.sr.ht";
-
-  drv = pkgs.sourcehut.hubsrht;
-in
-{
-  options.services.sourcehut.hub = {
-    user = mkOption {
-      type = types.str;
-      default = "hubsrht";
-      description = ''
-        User for hub.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5014;
-      description = ''
-        Port on which the "hub" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "hub.sr.ht";
-      description = ''
-        PostgreSQL database name for hub.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/hubsrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/hubsrht"'';
-      description = ''
-        State path for hub.sr.ht.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "hub" cfg.services) {
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          description = "hub.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0750 ${user} ${user} -"
-      ];
-
-      services.hubsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-        after = [ "postgresql.service" "network.target" ];
-        requires = [ "postgresql.service" ];
-        wantedBy = [ "multi-user.target" ];
-
-        description = "hub.sr.ht website service";
-
-        serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL hub.sr.ht is being served at (protocol://domain)
-      "hub.sr.ht".origin = mkDefault "http://hub.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "hub.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "hub.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "hub.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "hub.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # hub.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "hub.sr.ht".oauth-client-id = mkDefault null;
-      "hub.sr.ht".oauth-client-secret = mkDefault null;
-    };
-
-    services.nginx.virtualHosts."${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.hubsrht}/${pkgs.sourcehut.python.sitePackages}/hubsrht";
-    };
-    services.nginx.virtualHosts."hub.${cfg.originBase}" = {
-      globalRedirect = "${cfg.originBase}";
-      forceSSL = true;
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/lists.nix b/nixos/modules/services/misc/sourcehut/lists.nix
deleted file mode 100644
index 76f155caa05b..000000000000
--- a/nixos/modules/services/misc/sourcehut/lists.nix
+++ /dev/null
@@ -1,187 +0,0 @@
-# Email setup is fairly involved, useful references:
-# https://drewdevault.com/2018/08/05/Local-mail-server.html
-
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  cfgIni = cfg.settings;
-  scfg = cfg.lists;
-  iniKey = "lists.sr.ht";
-
-  rcfg = config.services.redis;
-  drv = pkgs.sourcehut.listssrht;
-in
-{
-  options.services.sourcehut.lists = {
-    user = mkOption {
-      type = types.str;
-      default = "listssrht";
-      description = ''
-        User for lists.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5006;
-      description = ''
-        Port on which the "lists" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "lists.sr.ht";
-      description = ''
-        PostgreSQL database name for lists.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/listssrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/listssrht"'';
-      description = ''
-        State path for lists.sr.ht.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "lists" cfg.services) {
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          extraGroups = [ "postfix" ];
-          description = "lists.sr.ht user";
-        };
-      };
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0750 ${user} ${user} -"
-      ];
-
-      services = {
-        listssrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "lists.sr.ht website service";
-
-          serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-        };
-
-        listssrht-process = {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "lists.sr.ht process service";
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Restart = "always";
-            ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.process worker --loglevel=info";
-          };
-        };
-
-        listssrht-lmtp = {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "lists.sr.ht process service";
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Restart = "always";
-            ExecStart = "${cfg.python}/bin/listssrht-lmtp";
-          };
-        };
-
-
-        listssrht-webhooks = {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "lists.sr.ht webhooks service";
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Restart = "always";
-            ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
-          };
-        };
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL lists.sr.ht is being served at (protocol://domain)
-      "lists.sr.ht".origin = mkDefault "http://lists.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "lists.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "lists.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "lists.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "lists.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # lists.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "lists.sr.ht".oauth-client-id = mkDefault null;
-      "lists.sr.ht".oauth-client-secret = mkDefault null;
-      # Outgoing email for notifications generated by users
-      "lists.sr.ht".notify-from = mkDefault "CHANGEME@example.org";
-      # The redis connection used for the webhooks worker
-      "lists.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/2";
-      # The redis connection used for the celery worker
-      "lists.sr.ht".redis = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/4";
-      # Network-key
-      "lists.sr.ht".network-key = mkDefault null;
-      # Allow creation
-      "lists.sr.ht".allow-new-lists = mkDefault "no";
-      # Posting Domain
-      "lists.sr.ht".posting-domain = mkDefault "lists.${cfg.originBase}";
-
-      # Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
-      # Alternatively, specify IP:PORT and an SMTP server will be run instead.
-      "lists.sr.ht::worker".sock = mkDefault "/tmp/lists.sr.ht-lmtp.sock";
-      # The lmtp daemon will make the unix socket group-read/write for users in this
-      # group.
-      "lists.sr.ht::worker".sock-group = mkDefault "postfix";
-      "lists.sr.ht::worker".reject-url = mkDefault "https://man.sr.ht/lists.sr.ht/etiquette.md";
-      "lists.sr.ht::worker".reject-mimetypes = mkDefault "text/html";
-
-    };
-
-    services.nginx.virtualHosts."lists.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.listssrht}/${pkgs.sourcehut.python.sitePackages}/listssrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/man.nix b/nixos/modules/services/misc/sourcehut/man.nix
deleted file mode 100644
index 8ca271c32ee3..000000000000
--- a/nixos/modules/services/misc/sourcehut/man.nix
+++ /dev/null
@@ -1,124 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  cfgIni = cfg.settings;
-  scfg = cfg.man;
-  iniKey = "man.sr.ht";
-
-  drv = pkgs.sourcehut.mansrht;
-in
-{
-  options.services.sourcehut.man = {
-    user = mkOption {
-      type = types.str;
-      default = "mansrht";
-      description = ''
-        User for man.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5004;
-      description = ''
-        Port on which the "man" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "man.sr.ht";
-      description = ''
-        PostgreSQL database name for man.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/mansrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/mansrht"'';
-      description = ''
-        State path for man.sr.ht.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "man" cfg.services) {
-    assertions =
-      [
-        {
-          assertion = hasAttrByPath [ "git.sr.ht" "oauth-client-id" ] cfgIni;
-          message = "man.sr.ht needs access to git.sr.ht.";
-        }
-      ];
-
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          description = "man.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0750 ${user} ${user} -"
-      ];
-
-      services.mansrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-        after = [ "postgresql.service" "network.target" ];
-        requires = [ "postgresql.service" ];
-        wantedBy = [ "multi-user.target" ];
-
-        description = "man.sr.ht website service";
-
-        serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL man.sr.ht is being served at (protocol://domain)
-      "man.sr.ht".origin = mkDefault "http://man.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "man.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "man.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "man.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "man.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # man.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "man.sr.ht".oauth-client-id = mkDefault null;
-      "man.sr.ht".oauth-client-secret = mkDefault null;
-    };
-
-    services.nginx.virtualHosts."man.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.mansrht}/${pkgs.sourcehut.python.sitePackages}/mansrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/meta.nix b/nixos/modules/services/misc/sourcehut/meta.nix
deleted file mode 100644
index 33e4f2332b53..000000000000
--- a/nixos/modules/services/misc/sourcehut/meta.nix
+++ /dev/null
@@ -1,213 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  cfgIni = cfg.settings;
-  scfg = cfg.meta;
-  iniKey = "meta.sr.ht";
-
-  rcfg = config.services.redis;
-  drv = pkgs.sourcehut.metasrht;
-in
-{
-  options.services.sourcehut.meta = {
-    user = mkOption {
-      type = types.str;
-      default = "metasrht";
-      description = ''
-        User for meta.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5000;
-      description = ''
-        Port on which the "meta" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "meta.sr.ht";
-      description = ''
-        PostgreSQL database name for meta.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/metasrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/metasrht"'';
-      description = ''
-        State path for meta.sr.ht.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "meta" cfg.services) {
-    assertions =
-      [
-        {
-          assertion = with cfgIni."meta.sr.ht::billing"; enabled == "yes" -> (stripe-public-key != null && stripe-secret-key != null);
-          message = "If meta.sr.ht::billing is enabled, the keys should be defined.";
-        }
-      ];
-
-    users = {
-      users = {
-        ${user} = {
-          isSystemUser = true;
-          group = user;
-          description = "meta.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.cron.systemCronJobs = [ "0 0 * * * ${cfg.python}/bin/metasrht-daily" ];
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0750 ${user} ${user} -"
-      ];
-
-      services = {
-        metasrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "meta.sr.ht website service";
-
-          preStart = ''
-            # Configure client(s) as "preauthorized"
-            ${concatMapStringsSep "\n\n"
-              (attr: ''
-                if ! test -e "${statePath}/${attr}.oauth" || [ "$(cat ${statePath}/${attr}.oauth)" != "${cfgIni."${attr}".oauth-client-id}" ]; then
-                  # Configure ${attr}'s OAuth client as "preauthorized"
-                  psql ${database} \
-                    -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${cfgIni."${attr}".oauth-client-id}'"
-
-                  printf "%s" "${cfgIni."${attr}".oauth-client-id}" > "${statePath}/${attr}.oauth"
-                fi
-              '')
-              (builtins.attrNames (filterAttrs
-                (k: v: !(hasInfix "::" k) && builtins.hasAttr "oauth-client-id" v && v.oauth-client-id != null)
-                cfg.settings))}
-          '';
-
-          serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-        };
-
-        metasrht-api = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "meta.sr.ht api service";
-
-          preStart = ''
-            # Configure client(s) as "preauthorized"
-            ${concatMapStringsSep "\n\n"
-              (attr: ''
-                if ! test -e "${statePath}/${attr}.oauth" || [ "$(cat ${statePath}/${attr}.oauth)" != "${cfgIni."${attr}".oauth-client-id}" ]; then
-                  # Configure ${attr}'s OAuth client as "preauthorized"
-                  psql ${database} \
-                    -c "UPDATE oauthclient SET preauthorized = true WHERE client_id = '${cfgIni."${attr}".oauth-client-id}'"
-
-                  printf "%s" "${cfgIni."${attr}".oauth-client-id}" > "${statePath}/${attr}.oauth"
-                fi
-              '')
-              (builtins.attrNames (filterAttrs
-                (k: v: !(hasInfix "::" k) && builtins.hasAttr "oauth-client-id" v && v.oauth-client-id != null)
-                cfg.settings))}
-          '';
-
-          serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b :${toString (port + 100)}";
-        };
-
-        metasrht-webhooks = {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "meta.sr.ht webhooks service";
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Restart = "always";
-            ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
-          };
-
-        };
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL meta.sr.ht is being served at (protocol://domain)
-      "meta.sr.ht".origin = mkDefault "https://meta.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "meta.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "meta.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "meta.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "meta.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # If "yes", the user will be sent the stock sourcehut welcome emails after
-      # signup (requires cron to be configured properly). These are specific to the
-      # sr.ht instance so you probably want to patch these before enabling this.
-      "meta.sr.ht".welcome-emails = mkDefault "no";
-
-      # The redis connection used for the webhooks worker
-      "meta.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/6";
-
-      # If "no", public registration will not be permitted.
-      "meta.sr.ht::settings".registration = mkDefault "no";
-      # Where to redirect new users upon registration
-      "meta.sr.ht::settings".onboarding-redirect = mkDefault "https://meta.${cfg.originBase}";
-      # How many invites each user is issued upon registration (only applicable if
-      # open registration is disabled)
-      "meta.sr.ht::settings".user-invites = mkDefault 5;
-
-      # Origin URL for API, 100 more than web
-      "meta.sr.ht".api-origin = mkDefault "http://localhost:5100";
-
-      # You can add aliases for the client IDs of commonly used OAuth clients here.
-      #
-      # Example:
-      "meta.sr.ht::aliases" = mkDefault { };
-      # "meta.sr.ht::aliases"."git.sr.ht" = 12345;
-
-      # "yes" to enable the billing system
-      "meta.sr.ht::billing".enabled = mkDefault "no";
-      # Get your keys at https://dashboard.stripe.com/account/apikeys
-      "meta.sr.ht::billing".stripe-public-key = mkDefault null;
-      "meta.sr.ht::billing".stripe-secret-key = mkDefault null;
-    };
-
-    services.nginx.virtualHosts."meta.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.metasrht}/${pkgs.sourcehut.python.sitePackages}/metasrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/paste.nix b/nixos/modules/services/misc/sourcehut/paste.nix
deleted file mode 100644
index b481ebaf8917..000000000000
--- a/nixos/modules/services/misc/sourcehut/paste.nix
+++ /dev/null
@@ -1,135 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  cfgIni = cfg.settings;
-  scfg = cfg.paste;
-  iniKey = "paste.sr.ht";
-
-  rcfg = config.services.redis;
-  drv = pkgs.sourcehut.pastesrht;
-in
-{
-  options.services.sourcehut.paste = {
-    user = mkOption {
-      type = types.str;
-      default = "pastesrht";
-      description = ''
-        User for paste.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5011;
-      description = ''
-        Port on which the "paste" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "paste.sr.ht";
-      description = ''
-        PostgreSQL database name for paste.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/pastesrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/pastesrht"'';
-      description = ''
-        State path for pastesrht.sr.ht.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "paste" cfg.services) {
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          description = "paste.sr.ht user";
-        };
-      };
-
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0750 ${user} ${user} -"
-      ];
-
-      services = {
-        pastesrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "paste.sr.ht website service";
-
-          serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-        };
-
-        pastesrht-webhooks = {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "paste.sr.ht webhooks service";
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Restart = "always";
-            ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
-          };
-
-        };
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL paste.sr.ht is being served at (protocol://domain)
-      "paste.sr.ht".origin = mkDefault "http://paste.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "paste.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "paste.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "paste.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "paste.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # paste.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "paste.sr.ht".oauth-client-id = mkDefault null;
-      "paste.sr.ht".oauth-client-secret = mkDefault null;
-      "paste.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/5";
-    };
-
-    services.nginx.virtualHosts."paste.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.pastesrht}/${pkgs.sourcehut.python.sitePackages}/pastesrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/sourcehut/service.nix b/nixos/modules/services/misc/sourcehut/service.nix
index f1706ad0a6a8..aae13e0cc2c9 100644
--- a/nixos/modules/services/misc/sourcehut/service.nix
+++ b/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,12 +117,12 @@ let
 in
 {
   options.services.sourcehut.${srv} = {
-    enable = mkEnableOption "${srv} service";
+    enable = mkEnableOption (lib.mdDoc "${srv} service");
 
     user = mkOption {
       type = types.str;
       default = srvsrht;
-      description = ''
+      description = lib.mdDoc ''
         User for ${srv}.sr.ht.
       '';
     };
@@ -130,7 +130,7 @@ in
     group = mkOption {
       type = types.str;
       default = srvsrht;
-      description = ''
+      description = lib.mdDoc ''
         Group for ${srv}.sr.ht.
         Membership grants access to the Git/Mercurial repositories by default,
         but not to the config.ini file (where secrets are).
@@ -140,7 +140,7 @@ in
     port = mkOption {
       type = types.port;
       default = port;
-      description = ''
+      description = lib.mdDoc ''
         Port on which the "${srv}" backend should listen.
       '';
     };
@@ -148,9 +148,9 @@ in
     redis = {
       host = mkOption {
         type = types.str;
-        default = "unix:/run/redis-sourcehut-${srvsrht}/redis.sock?db=0";
+        default = "unix:///run/redis-sourcehut-${srvsrht}/redis.sock?db=0";
         example = "redis://shared.wireguard:6379/0";
-        description = ''
+        description = lib.mdDoc ''
           The redis host URL. This is used for caching and temporary storage, and must
           be shared between nodes (e.g. git1.sr.ht and git2.sr.ht), but need not be
           shared between services. It may be shared between services, however, with no
@@ -163,9 +163,9 @@ in
       database = mkOption {
         type = types.str;
         default = "${srv}.sr.ht";
-        description = ''
+        description = lib.mdDoc ''
           PostgreSQL database name for the ${srv}.sr.ht service,
-          used if <xref linkend="opt-services.sourcehut.postgresql.enable"/> is <literal>true</literal>.
+          used if [](#opt-services.sourcehut.postgresql.enable) is `true`.
         '';
       };
     };
@@ -174,7 +174,7 @@ in
       extraArgs = mkOption {
         type = with types; listOf str;
         default = ["--timeout 120" "--workers 1" "--log-level=info"];
-        description = "Extra arguments passed to Gunicorn.";
+        description = lib.mdDoc "Extra arguments passed to Gunicorn.";
       };
     };
   } // optionalAttrs webhooks {
@@ -182,12 +182,12 @@ in
       extraArgs = mkOption {
         type = with types; listOf str;
         default = ["--loglevel DEBUG" "--pool eventlet" "--without-heartbeat"];
-        description = "Extra arguments passed to the Celery responsible for webhooks.";
+        description = lib.mdDoc "Extra arguments passed to the Celery responsible for webhooks.";
       };
       celeryConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Content of the <literal>celeryconfig.py</literal> used by the Celery responsible for webhooks.";
+        description = lib.mdDoc "Content of the `celeryconfig.py` used by the Celery responsible for webhooks.";
       };
     };
   };
diff --git a/nixos/modules/services/misc/sourcehut/todo.nix b/nixos/modules/services/misc/sourcehut/todo.nix
deleted file mode 100644
index 262fa48f59d4..000000000000
--- a/nixos/modules/services/misc/sourcehut/todo.nix
+++ /dev/null
@@ -1,163 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.sourcehut;
-  opt = options.services.sourcehut;
-  cfgIni = cfg.settings;
-  scfg = cfg.todo;
-  iniKey = "todo.sr.ht";
-
-  rcfg = config.services.redis;
-  drv = pkgs.sourcehut.todosrht;
-in
-{
-  options.services.sourcehut.todo = {
-    user = mkOption {
-      type = types.str;
-      default = "todosrht";
-      description = ''
-        User for todo.sr.ht.
-      '';
-    };
-
-    port = mkOption {
-      type = types.port;
-      default = 5003;
-      description = ''
-        Port on which the "todo" module should listen.
-      '';
-    };
-
-    database = mkOption {
-      type = types.str;
-      default = "todo.sr.ht";
-      description = ''
-        PostgreSQL database name for todo.sr.ht.
-      '';
-    };
-
-    statePath = mkOption {
-      type = types.path;
-      default = "${cfg.statePath}/todosrht";
-      defaultText = literalExpression ''"''${config.${opt.statePath}}/todosrht"'';
-      description = ''
-        State path for todo.sr.ht.
-      '';
-    };
-  };
-
-  config = with scfg; lib.mkIf (cfg.enable && elem "todo" cfg.services) {
-    users = {
-      users = {
-        "${user}" = {
-          isSystemUser = true;
-          group = user;
-          extraGroups = [ "postfix" ];
-          description = "todo.sr.ht user";
-        };
-      };
-      groups = {
-        "${user}" = { };
-      };
-    };
-
-    services.postgresql = {
-      authentication = ''
-        local ${database} ${user} trust
-      '';
-      ensureDatabases = [ database ];
-      ensureUsers = [
-        {
-          name = user;
-          ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
-        }
-      ];
-    };
-
-    systemd = {
-      tmpfiles.rules = [
-        "d ${statePath} 0750 ${user} ${user} -"
-      ];
-
-      services = {
-        todosrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "todo.sr.ht website service";
-
-          serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
-        };
-
-       todosrht-lmtp = {
-         after = [ "postgresql.service" "network.target" ];
-         bindsTo = [ "postgresql.service" ];
-         wantedBy = [ "multi-user.target" ];
-
-         description = "todo.sr.ht process service";
-         serviceConfig = {
-           Type = "simple";
-           User = user;
-           Restart = "always";
-           ExecStart = "${cfg.python}/bin/todosrht-lmtp";
-         };
-       };
-
-        todosrht-webhooks = {
-          after = [ "postgresql.service" "network.target" ];
-          requires = [ "postgresql.service" ];
-          wantedBy = [ "multi-user.target" ];
-
-          description = "todo.sr.ht webhooks service";
-          serviceConfig = {
-            Type = "simple";
-            User = user;
-            Restart = "always";
-            ExecStart = "${cfg.python}/bin/celery -A ${drv.pname}.webhooks worker --loglevel=info";
-          };
-
-        };
-      };
-    };
-
-    services.sourcehut.settings = {
-      # URL todo.sr.ht is being served at (protocol://domain)
-      "todo.sr.ht".origin = mkDefault "http://todo.${cfg.originBase}";
-      # Address and port to bind the debug server to
-      "todo.sr.ht".debug-host = mkDefault "0.0.0.0";
-      "todo.sr.ht".debug-port = mkDefault port;
-      # Configures the SQLAlchemy connection string for the database.
-      "todo.sr.ht".connection-string = mkDefault "postgresql:///${database}?user=${user}&host=/var/run/postgresql";
-      # Set to "yes" to automatically run migrations on package upgrade.
-      "todo.sr.ht".migrate-on-upgrade = mkDefault "yes";
-      # todo.sr.ht's OAuth client ID and secret for meta.sr.ht
-      # Register your client at meta.example.org/oauth
-      "todo.sr.ht".oauth-client-id = mkDefault null;
-      "todo.sr.ht".oauth-client-secret = mkDefault null;
-      # Outgoing email for notifications generated by users
-      "todo.sr.ht".notify-from = mkDefault "CHANGEME@example.org";
-      # The redis connection used for the webhooks worker
-      "todo.sr.ht".webhooks = mkDefault "redis://${rcfg.bind}:${toString rcfg.port}/1";
-      # Network-key
-      "todo.sr.ht".network-key = mkDefault null;
-
-      # Path for the lmtp daemon's unix socket. Direct incoming mail to this socket.
-      # Alternatively, specify IP:PORT and an SMTP server will be run instead.
-      "todo.sr.ht::mail".sock = mkDefault "/tmp/todo.sr.ht-lmtp.sock";
-      # The lmtp daemon will make the unix socket group-read/write for users in this
-      # group.
-      "todo.sr.ht::mail".sock-group = mkDefault "postfix";
-
-      "todo.sr.ht::mail".posting-domain = mkDefault "todo.${cfg.originBase}";
-    };
-
-    services.nginx.virtualHosts."todo.${cfg.originBase}" = {
-      forceSSL = true;
-      locations."/".proxyPass = "http://${cfg.address}:${toString port}";
-      locations."/query".proxyPass = "http://${cfg.address}:${toString (port + 100)}";
-      locations."/static".root = "${pkgs.sourcehut.todosrht}/${pkgs.sourcehut.python.sitePackages}/todosrht";
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/spice-vdagentd.nix b/nixos/modules/services/misc/spice-vdagentd.nix
index 2dd9fcf68ab0..bde64847d89e 100644
--- a/nixos/modules/services/misc/spice-vdagentd.nix
+++ b/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/nixos/modules/services/misc/spice-webdavd.nix b/nixos/modules/services/misc/spice-webdavd.nix
new file mode 100644
index 000000000000..6c817e429ac6
--- /dev/null
+++ b/nixos/modules/services/misc/spice-webdavd.nix
@@ -0,0 +1,38 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.spice-webdavd;
+in
+{
+  options = {
+    services.spice-webdavd = {
+      enable = mkEnableOption (lib.mdDoc "the spice guest webdav proxy daemon");
+
+      package = mkOption {
+        default = pkgs.phodav;
+        defaultText = literalExpression "pkgs.phodav";
+        type = types.package;
+        description = lib.mdDoc "spice-webdavd provider package to use.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # ensure the webdav fs this exposes can actually be mounted
+    services.davfs2.enable = true;
+
+    # add the udev rule which starts the proxy when the spice socket is present
+    services.udev.packages = [ cfg.package ];
+
+    systemd.services.spice-webdavd = {
+      description = "spice-webdav proxy daemon";
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${cfg.package}/bin/spice-webdavd -p 9843";
+        Restart = "on-success";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/ssm-agent.nix b/nixos/modules/services/misc/ssm-agent.nix
index 4ae596ade174..d1f371c2bd61 100644
--- a/nixos/modules/services/misc/ssm-agent.nix
+++ b/nixos/modules/services/misc/ssm-agent.nix
@@ -17,11 +17,11 @@ let
   '';
 in {
   options.services.ssm-agent = {
-    enable = mkEnableOption "AWS SSM agent";
+    enable = mkEnableOption (lib.mdDoc "AWS SSM agent");
 
     package = mkOption {
       type = types.path;
-      description = "The SSM agent package to use";
+      description = lib.mdDoc "The SSM agent package to use";
       default = pkgs.ssm-agent.override { overrideEtc = false; };
       defaultText = literalExpression "pkgs.ssm-agent.override { overrideEtc = false; }";
     };
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index 386281e2b7cc..edd5750a4a47 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -3,14 +3,18 @@ with lib;
 let
   cfg = config.services.sssd;
   nscd = config.services.nscd;
+
+  dataDir = "/var/lib/sssd";
+  settingsFile = "${dataDir}/sssd.conf";
+  settingsFileUnsubstituted = pkgs.writeText "${dataDir}/sssd-unsubstituted.conf" cfg.config;
 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;
-        description = "Contents of <filename>sssd.conf</filename>.";
+        description = lib.mdDoc "Contents of {file}`sssd.conf`.";
         default = ''
           [sssd]
           config_file_version = 2
@@ -33,9 +37,40 @@ in {
       sshAuthorizedKeysIntegration = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to make sshd look up authorized keys from SSS.
-          For this to work, the <literal>ssh</literal> SSS service must be enabled in the sssd configuration.
+          For this to work, the `ssh` SSS service must be enabled in the sssd configuration.
+        '';
+      };
+
+      kcm = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to use SSS as a Kerberos Cache Manager (KCM).
+          Kerberos will be configured to cache credentials in SSS.
+        '';
+      };
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        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.
+
+          ```
+            # snippet of sssd-related config
+            [domain/LDAP]
+            ldap_default_authtok = $SSSD_LDAP_DEFAULT_AUTHTOK
+          ```
+
+          ```
+            # contents of the environment file
+            SSSD_LDAP_DEFAULT_AUTHTOK=verysecretpassword
+          ```
         '';
       };
     };
@@ -51,22 +86,29 @@ in {
         wants = [ "nss-user-lookup.target" ];
         restartTriggers = [
           config.environment.etc."nscd.conf".source
-          config.environment.etc."sssd/sssd.conf".source
+          settingsFileUnsubstituted
         ];
         script = ''
           export LDB_MODULES_PATH+="''${LDB_MODULES_PATH+:}${pkgs.ldb}/modules/ldb:${pkgs.sssd}/modules/ldb"
           mkdir -p /var/lib/sss/{pubconf,db,mc,pipes,gpo_cache,secrets} /var/lib/sss/pipes/private /var/lib/sss/pubconf/krb5.include.d
-          ${pkgs.sssd}/bin/sssd -D
+          ${pkgs.sssd}/bin/sssd -D -c ${settingsFile}
         '';
         serviceConfig = {
           Type = "forking";
           PIDFile = "/run/sssd.pid";
+          StateDirectory = baseNameOf dataDir;
+          # We cannot use LoadCredential here because it's not available in ExecStartPre
+          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
         };
-      };
-
-      environment.etc."sssd/sssd.conf" = {
-        text = cfg.config;
-        mode = "0400";
+        preStart = ''
+          [ -f ${settingsFile} ] && rm -f ${settingsFile}
+          old_umask=$(umask)
+          umask 0177
+          ${pkgs.envsubst}/bin/envsubst \
+            -o ${settingsFile} \
+            -i ${settingsFileUnsubstituted}
+          umask $old_umask
+        '';
       };
 
       system.nssModules = [ pkgs.sssd ];
@@ -79,6 +121,28 @@ in {
       services.dbus.packages = [ pkgs.sssd ];
     })
 
+    (mkIf cfg.kcm {
+      systemd.services.sssd-kcm = {
+        description = "SSSD Kerberos Cache Manager";
+        requires = [ "sssd-kcm.socket" ];
+        serviceConfig = {
+          ExecStartPre = "-${pkgs.sssd}/bin/sssd --genconf-section=kcm";
+          ExecStart = "${pkgs.sssd}/libexec/sssd/sssd_kcm --uid 0 --gid 0";
+        };
+        restartTriggers = [
+          config.environment.etc."sssd/sssd.conf".source
+        ];
+      };
+      systemd.sockets.sssd-kcm = {
+        description = "SSSD Kerberos Cache Manager responder socket";
+        wantedBy = [ "sockets.target" ];
+        # Matches the default in MIT krb5 and Heimdal:
+        # https://github.com/krb5/krb5/blob/krb5-1.19.3-final/src/include/kcm.h#L43
+        listenStreams = [ "/var/run/.heim_org.h5l.kcm-socket" ];
+      };
+      krb5.libdefaults.default_ccache_name = "KCM:";
+    })
+
     (mkIf cfg.sshAuthorizedKeysIntegration {
     # Ugly: sshd refuses to start if a store path is given because /nix/store is group-writable.
     # So indirect by a symlink.
diff --git a/nixos/modules/services/misc/subsonic.nix b/nixos/modules/services/misc/subsonic.nix
index 2dda8970dd30..0862d5782595 100644
--- a/nixos/modules/services/misc/subsonic.nix
+++ b/nixos/modules/services/misc/subsonic.nix
@@ -8,12 +8,12 @@ let
 in {
   options = {
     services.subsonic = {
-      enable = mkEnableOption "Subsonic daemon";
+      enable = mkEnableOption (lib.mdDoc "Subsonic daemon");
 
       home = mkOption {
         type = types.path;
         default = "/var/lib/subsonic";
-        description = ''
+        description = lib.mdDoc ''
           The directory where Subsonic will create files.
           Make sure it is writable.
         '';
@@ -22,7 +22,7 @@ in {
       listenAddress = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           The host name or IP address on which to bind Subsonic.
           Only relevant if you have multiple network interfaces and want
           to make Subsonic available on only one of them. The default value
@@ -33,7 +33,7 @@ in {
       port = mkOption {
         type = types.port;
         default = 4040;
-        description = ''
+        description = lib.mdDoc ''
           The port on which Subsonic will listen for
           incoming HTTP traffic. Set to 0 to disable.
         '';
@@ -42,7 +42,7 @@ in {
       httpsPort = mkOption {
         type = types.port;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           The port on which Subsonic will listen for
           incoming HTTPS traffic. Set to 0 to disable.
         '';
@@ -51,7 +51,7 @@ in {
       contextPath = mkOption {
         type = types.path;
         default = "/";
-        description = ''
+        description = lib.mdDoc ''
           The context path, i.e., the last part of the Subsonic
           URL. Typically '/' or '/subsonic'. Default '/'
         '';
@@ -60,7 +60,7 @@ in {
       maxMemory = mkOption {
         type = types.int;
         default = 100;
-        description = ''
+        description = lib.mdDoc ''
           The memory limit (max Java heap size) in megabytes.
           Default: 100
         '';
@@ -69,7 +69,7 @@ in {
       defaultMusicFolder = mkOption {
         type = types.path;
         default = "/var/music";
-        description = ''
+        description = lib.mdDoc ''
           Configure Subsonic to use this folder for music.  This option
           only has effect the first time Subsonic is started.
         '';
@@ -78,7 +78,7 @@ in {
       defaultPodcastFolder = mkOption {
         type = types.path;
         default = "/var/music/Podcast";
-        description = ''
+        description = lib.mdDoc ''
           Configure Subsonic to use this folder for Podcasts.  This option
           only has effect the first time Subsonic is started.
         '';
@@ -87,7 +87,7 @@ in {
       defaultPlaylistFolder = mkOption {
         type = types.path;
         default = "/var/playlists";
-        description = ''
+        description = lib.mdDoc ''
           Configure Subsonic to use this folder for playlists.  This option
           only has effect the first time Subsonic is started.
         '';
@@ -97,7 +97,7 @@ in {
         type = types.listOf types.path;
         default = [ "${pkgs.ffmpeg.bin}/bin/ffmpeg" ];
         defaultText = literalExpression ''[ "''${pkgs.ffmpeg.bin}/bin/ffmpeg" ]'';
-        description = ''
+        description = lib.mdDoc ''
           List of paths to transcoder executables that should be accessible
           from Subsonic. Symlinks will be created to each executable inside
           ''${config.${opt.home}}/transcoders.
diff --git a/nixos/modules/services/misc/sundtek.nix b/nixos/modules/services/misc/sundtek.nix
index e3234518c940..e85d7c5b92b9 100644
--- a/nixos/modules/services/misc/sundtek.nix
+++ b/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/nixos/modules/services/misc/svnserve.nix b/nixos/modules/services/misc/svnserve.nix
index 5fa262ca3b94..a0103641c650 100644
--- a/nixos/modules/services/misc/svnserve.nix
+++ b/nixos/modules/services/misc/svnserve.nix
@@ -20,13 +20,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable svnserve to serve Subversion repositories through the SVN protocol.";
+        description = lib.mdDoc "Whether to enable svnserve to serve Subversion repositories through the SVN protocol.";
       };
 
       svnBaseDir = mkOption {
         type = types.str;
         default = "/repos";
-        description = "Base directory from which Subversion repositories are accessed.";
+        description = lib.mdDoc "Base directory from which Subversion repositories are accessed.";
       };
     };
 
diff --git a/nixos/modules/services/misc/synergy.nix b/nixos/modules/services/misc/synergy.nix
index d6cd5d7f0d66..0cbdc7599c0f 100644
--- a/nixos/modules/services/misc/synergy.nix
+++ b/nixos/modules/services/misc/synergy.nix
@@ -19,19 +19,19 @@ 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 = "";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             Use the given name instead of the hostname to identify
             ourselves to the server.
           '';
         };
         serverAddress = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The server address is of the form: [hostname][:port].  The
             hostname must be the address or hostname of the server.  The
             port overrides the default port, 24800.
@@ -40,22 +40,22 @@ in
         autoStart = mkOption {
           default = true;
           type = types.bool;
-          description = "Whether the Synergy client should be started automatically.";
+          description = lib.mdDoc "Whether the Synergy client should be started automatically.";
         };
       };
 
       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;
           default = "/etc/synergy-server.conf";
-          description = "The Synergy server configuration file.";
+          description = lib.mdDoc "The Synergy server configuration file.";
         };
         screenName = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Use the given name instead of the hostname to identify
             this screen in the configuration.
           '';
@@ -63,18 +63,18 @@ in
         address = mkOption {
           type = types.str;
           default = "";
-          description = "Address on which to listen for clients.";
+          description = lib.mdDoc "Address on which to listen for clients.";
         };
         autoStart = mkOption {
           default = true;
           type = types.bool;
-          description = "Whether the Synergy server should be started automatically.";
+          description = lib.mdDoc "Whether the Synergy server should be started automatically.";
         };
         tls = {
           enable = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether TLS encryption should be used.
 
               Using this requires a TLS certificate that can be
@@ -87,7 +87,7 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "~/.synergy/SSL/Synergy.pem";
-            description = "The TLS certificate to use for encryption.";
+            description = lib.mdDoc "The TLS certificate to use for encryption.";
           };
         };
       };
@@ -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/nixos/modules/services/misc/sysprof.nix b/nixos/modules/services/misc/sysprof.nix
index ab91a8b586a2..25c5b0fabf61 100644
--- a/nixos/modules/services/misc/sysprof.nix
+++ b/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/nixos/modules/services/misc/tandoor-recipes.nix b/nixos/modules/services/misc/tandoor-recipes.nix
new file mode 100644
index 000000000000..a349bcac9321
--- /dev/null
+++ b/nixos/modules/services/misc/tandoor-recipes.nix
@@ -0,0 +1,144 @@
+{ 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";
+    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/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index e20804929981..ee4bf42183f9 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/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.
     '';
   };
@@ -89,7 +91,7 @@ let
       type = types.uniq (types.listOf types.str);
       default = [];
       example = [ "alice" "bob" ];
-      description = ''
+      description = lib.mdDoc ''
         A list of user names that belong to the organization.
       '';
     };
@@ -98,7 +100,7 @@ let
       type = types.listOf types.str;
       default = [];
       example = [ "workers" "slackers" ];
-      description = ''
+      description = lib.mdDoc ''
         A list of group names that belong to the organization.
       '';
     };
@@ -140,30 +142,30 @@ 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}).
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "taskd";
-        description = "User for Taskserver.";
+        description = lib.mdDoc "User for Taskserver.";
       };
 
       group = mkOption {
         type = types.str;
         default = "taskd";
-        description = "Group for Taskserver.";
+        description = lib.mdDoc "Group for Taskserver.";
       };
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/taskserver";
-        description = "Data directory for Taskserver.";
+        description = lib.mdDoc "Data directory for Taskserver.";
       };
 
       ciphers = mkOption {
@@ -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.
         '';
       };
 
@@ -184,17 +186,17 @@ in {
         example.myShinyOrganisation.users = [ "alice" "bob" ];
         example.myShinyOrganisation.groups = [ "staff" "outsiders" ];
         example.yetAnotherOrganisation.users = [ "foo" "bar" ];
-        description = ''
+        description = lib.mdDoc ''
           An attribute set where the keys name the organisation and the values
-          are a set of lists of <option>users</option> and
-          <option>groups</option>.
+          are a set of lists of {option}`users` and
+          {option}`groups`.
         '';
       };
 
       confirmation = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Determines whether certain commands are confirmed.
         '';
       };
@@ -202,7 +204,7 @@ in {
       debug = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Logs debugging information.
         '';
       };
@@ -210,7 +212,7 @@ in {
       extensions = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Fully qualified path of the Taskserver extension scripts.
           Currently there are none.
         '';
@@ -219,7 +221,7 @@ in {
       ipLog = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Logs the IP addresses of incoming requests.
         '';
       };
@@ -227,18 +229,15 @@ in {
       queueSize = mkOption {
         type = types.int;
         default = 10;
-        description = ''
-          Size of the connection backlog, see <citerefentry>
-            <refentrytitle>listen</refentrytitle>
-            <manvolnum>2</manvolnum>
-          </citerefentry>.
+        description = lib.mdDoc ''
+          Size of the connection backlog, see {manpage}`listen(2)`.
         '';
       };
 
       requestLimit = mkOption {
         type = types.int;
         default = 1048576;
-        description = ''
+        description = lib.mdDoc ''
           Size limit of incoming requests, in bytes.
         '';
       };
@@ -247,13 +246,13 @@ in {
         type = with types; either str (listOf str);
         default = [];
         example = [ "[Tt]ask [2-9]+" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of regular expressions that are matched against the reported
-          client id (such as <literal>task 2.3.0</literal>).
+          client id (such as `task 2.3.0`).
 
-          The values <literal>all</literal> or <literal>none</literal> have
-          special meaning. Overidden by any entry in the option
-          <option>services.taskserver.disallowedClientIDs</option>.
+          The values `all` or `none` have
+          special meaning. Overridden by any entry in the option
+          {option}`services.taskserver.disallowedClientIDs`.
         '';
       };
 
@@ -261,13 +260,13 @@ in {
         type = with types; either str (listOf str);
         default = [];
         example = [ "[Tt]ask [2-9]+" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of regular expressions that are matched against the reported
-          client id (such as <literal>task 2.3.0</literal>).
+          client id (such as `task 2.3.0`).
 
-          The values <literal>all</literal> or <literal>none</literal> have
+          The values `all` or `none` have
           special meaning. Any entry here overrides those in
-          <option>services.taskserver.allowedClientIDs</option>.
+          {option}`services.taskserver.allowedClientIDs`.
         '';
       };
 
@@ -275,7 +274,7 @@ in {
         type = types.str;
         default = "localhost";
         example = "::";
-        description = ''
+        description = lib.mdDoc ''
           The address (IPv4, IPv6 or DNS) to listen on.
         '';
       };
@@ -283,7 +282,7 @@ in {
       listenPort = mkOption {
         type = types.int;
         default = 53589;
-        description = ''
+        description = lib.mdDoc ''
           Port number of the Taskserver.
         '';
       };
@@ -291,7 +290,7 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to open the firewall for the specified Taskserver port.
         '';
       };
@@ -299,7 +298,7 @@ in {
       fqdn = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           The fully qualified domain name of this server, which is also used
           as the common name in the certificates.
         '';
@@ -308,12 +307,12 @@ in {
       trust = mkOption {
         type = types.enum [ "allow all" "strict" ];
         default = "strict";
-        description = ''
+        description = lib.mdDoc ''
           Determines how client certificates are validated.
 
-          The value <literal>allow all</literal> performs no client
+          The value `allow all` performs no client
           certificate validation. This is not recommended. The value
-          <literal>strict</literal> causes the client certificate to be
+          `strict` causes the client certificate to be
           validated against a CA.
         '';
       };
@@ -324,18 +323,16 @@ in {
       config = mkOption {
         type = types.attrs;
         example.client.cert = "/tmp/debugging.cert";
-        description = ''
+        description = lib.mdDoc ''
           Configuration options to pass to Taskserver.
 
-          The options here are the same as described in <citerefentry>
-            <refentrytitle>taskdrc</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>, but with one difference:
+          The options here are the same as described in
+          {manpage}`taskdrc(5)`, but with one difference:
 
-          The <literal>server</literal> option is
-          <literal>server.listen</literal> here, because the
-          <literal>server</literal> option would collide with other options
-          like <literal>server.cert</literal> and we would run in a type error
+          The `server` option is
+          `server.listen` here, because the
+          `server` option would collide with other options
+          like `server.cert` and we would run in a type error
           (attribute set versus string).
 
           Nix types like integers or booleans are automatically converted to
diff --git a/nixos/modules/services/misc/tautulli.nix b/nixos/modules/services/misc/tautulli.nix
index 9a972b291225..b29e9dc0c8d5 100644
--- a/nixos/modules/services/misc/tautulli.nix
+++ b/nixos/modules/services/misc/tautulli.nix
@@ -12,43 +12,49 @@ in
 
   options = {
     services.tautulli = {
-      enable = mkEnableOption "Tautulli Plex Monitor";
+      enable = mkEnableOption (lib.mdDoc "Tautulli Plex Monitor");
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/plexpy";
-        description = "The directory where Tautulli stores its data files.";
+        description = lib.mdDoc "The directory where Tautulli stores its data files.";
       };
 
       configFile = mkOption {
         type = types.str;
         default = "/var/lib/plexpy/config.ini";
-        description = "The location of Tautulli's config file.";
+        description = lib.mdDoc "The location of Tautulli's config file.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8181;
-        description = "TCP port where Tautulli listens.";
+        description = lib.mdDoc "TCP port where Tautulli listens.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Open ports in the firewall for Tautulli.";
       };
 
       user = mkOption {
         type = types.str;
         default = "plexpy";
-        description = "User account under which Tautulli runs.";
+        description = lib.mdDoc "User account under which Tautulli runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "nogroup";
-        description = "Group under which Tautulli runs.";
+        description = lib.mdDoc "Group under which Tautulli runs.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.tautulli;
         defaultText = literalExpression "pkgs.tautulli";
-        description = ''
+        description = lib.mdDoc ''
           The Tautulli package to use.
         '';
       };
@@ -74,6 +80,8 @@ in
       };
     };
 
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
     users.users = mkIf (cfg.user == "plexpy") {
       plexpy = { group = cfg.group; uid = config.ids.uids.plexpy; };
     };
diff --git a/nixos/modules/services/misc/tiddlywiki.nix b/nixos/modules/services/misc/tiddlywiki.nix
index 2adc08f6cfed..849f53ca2d48 100644
--- a/nixos/modules/services/misc/tiddlywiki.nix
+++ b/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;
@@ -24,9 +24,9 @@ in {
         readers="(authenticated)";
         port = 3456;
       };
-      description = ''
-        Parameters passed to <literal>--listen</literal> command.
-        Refer to <link xlink:href="https://tiddlywiki.com/#WebServer"/>
+      description = lib.mdDoc ''
+        Parameters passed to `--listen` command.
+        Refer to <https://tiddlywiki.com/#WebServer>
         for details on supported values.
       '';
     };
diff --git a/nixos/modules/services/misc/tp-auto-kbbl.nix b/nixos/modules/services/misc/tp-auto-kbbl.nix
index 59018f7f81ff..8d92d3d93677 100644
--- a/nixos/modules/services/misc/tp-auto-kbbl.nix
+++ b/nixos/modules/services/misc/tp-auto-kbbl.nix
@@ -9,27 +9,27 @@ 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;
         default = pkgs.tp-auto-kbbl;
         defaultText = literalExpression "pkgs.tp-auto-kbbl";
-        description = "Package providing <command>tp-auto-kbbl</command>.";
+        description = lib.mdDoc "Package providing {command}`tp-auto-kbbl`.";
       };
 
       arguments = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = ''
-          List of arguments appended to <literal>./tp-auto-kbbl --device [device] [arguments]</literal>
+        description = lib.mdDoc ''
+          List of arguments appended to `./tp-auto-kbbl --device [device] [arguments]`
         '';
       };
 
       device = mkOption {
         type = types.str;
         default = "/dev/input/event0";
-        description = "Device watched for activities.";
+        description = lib.mdDoc "Device watched for activities.";
       };
 
     };
diff --git a/nixos/modules/services/misc/tzupdate.nix b/nixos/modules/services/misc/tzupdate.nix
index eac1e1112a5a..300a578f7c4a 100644
--- a/nixos/modules/services/misc/tzupdate.nix
+++ b/nixos/modules/services/misc/tzupdate.nix
@@ -9,7 +9,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable the tzupdate timezone updating service. This provides
         a one-shot service which can be activated with systemctl to
         update the timezone.
diff --git a/nixos/modules/services/misc/uhub.nix b/nixos/modules/services/misc/uhub.nix
index 0d0a8c2a4cb8..80266b024e35 100644
--- a/nixos/modules/services/misc/uhub.nix
+++ b/nixos/modules/services/misc/uhub.nix
@@ -15,21 +15,21 @@ in {
 
     services.uhub = mkOption {
       default = { };
-      description = "Uhub ADC hub instances";
+      description = lib.mdDoc "Uhub ADC hub instances";
       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;
             default = false;
-            description = "Whether to enable TLS support.";
+            description = lib.mdDoc "Whether to enable TLS support.";
           };
 
           settings = mkOption {
             inherit (settingsFormat) type;
-            description = ''
+            description = lib.mdDoc ''
               Configuration of uhub.
               See https://www.uhub.org/doc/config.php for a list of options.
             '';
@@ -44,7 +44,7 @@ in {
           };
 
           plugins = mkOption {
-            description = "Uhub plugin configuration.";
+            description = lib.mdDoc "Uhub plugin configuration.";
             type = with types;
               listOf (submodule {
                 options = {
@@ -52,10 +52,10 @@ in {
                     type = path;
                     example = literalExpression
                       "$${pkgs.uhub}/plugins/mod_auth_sqlite.so";
-                    description = "Path to plugin file.";
+                    description = lib.mdDoc "Path to plugin file.";
                   };
                   settings = mkOption {
-                    description = "Settings specific to this plugin.";
+                    description = lib.mdDoc "Settings specific to this plugin.";
                     type = with types; attrsOf str;
                     example = { file = "/etc/uhub/users.db"; };
                   };
@@ -80,11 +80,12 @@ in {
           tls_enable = cfg.enableTLS;
           file_plugins = pkgs.writeText "uhub-plugins.conf"
             (lib.strings.concatStringsSep "\n" (map ({ plugin, settings }:
-              "plugin ${plugin} ${
-                toString
-                (lib.attrsets.mapAttrsToList (key: value: ''"${key}=${value}"'')
-                  settings)
-              }") cfg.plugins));
+              ''
+                plugin ${plugin} "${
+                  toString
+                  (lib.attrsets.mapAttrsToList (key: value: "${key}=${value}")
+                    settings)
+                }"'') cfg.plugins));
         };
       in {
         name = "uhub/${name}.conf";
@@ -104,6 +105,9 @@ in {
           ExecStart = "${pkg}/bin/uhub -c /etc/uhub/${name}.conf -L";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           DynamicUser = true;
+
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
         };
       };
     }) hubs;
diff --git a/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix
index 7a4c4dca2ac9..663a767a0c18 100644
--- a/nixos/modules/services/misc/weechat.nix
+++ b/nixos/modules/services/misc/weechat.nix
@@ -8,20 +8,20 @@ in
 
 {
   options.services.weechat = {
-    enable = mkEnableOption "weechat";
+    enable = mkEnableOption (lib.mdDoc "weechat");
     root = mkOption {
-      description = "Weechat state directory.";
+      description = lib.mdDoc "Weechat state directory.";
       type = types.str;
       default = "/var/lib/weechat";
     };
     sessionName = mkOption {
-      description = "Name of the `screen' session for weechat.";
+      description = lib.mdDoc "Name of the `screen' session for weechat.";
       default = "weechat-screen";
       type = types.str;
     };
     binary = mkOption {
       type = types.path;
-      description = "Binary to execute.";
+      description = lib.mdDoc "Binary to execute.";
       default = "${pkgs.weechat}/bin/weechat";
       defaultText = literalExpression ''"''${pkgs.weechat}/bin/weechat"'';
       example = literalExpression ''"''${pkgs.weechat}/bin/weechat-headless"'';
diff --git a/nixos/modules/services/misc/xmr-stak.nix b/nixos/modules/services/misc/xmr-stak.nix
index 9256e9ae01cb..6e123cf0380c 100644
--- a/nixos/modules/services/misc/xmr-stak.nix
+++ b/nixos/modules/services/misc/xmr-stak.nix
@@ -15,15 +15,15 @@ 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;
         default = [];
         example = [ "--noCPU" "--currency monero" ];
-        description = "List of parameters to pass to xmr-stak.";
+        description = lib.mdDoc "List of parameters to pass to xmr-stak.";
       };
 
       configFiles = mkOption {
@@ -52,7 +52,7 @@ in
             ''';
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Content of config files like config.txt, pools.txt or cpu.txt.
         '';
       };
diff --git a/nixos/modules/services/misc/xmrig.nix b/nixos/modules/services/misc/xmrig.nix
index c5c3803920c8..d2aa3df45d53 100644
--- a/nixos/modules/services/misc/xmrig.nix
+++ b/nixos/modules/services/misc/xmrig.nix
@@ -13,14 +13,14 @@ with lib;
 {
   options = {
     services.xmrig = {
-      enable = mkEnableOption "XMRig Mining Software";
+      enable = mkEnableOption (lib.mdDoc "XMRig Mining Software");
 
       package = mkOption {
         type = types.package;
         default = pkgs.xmrig;
         defaultText = literalExpression "pkgs.xmrig";
         example = literalExpression "pkgs.xmrig-mo";
-        description = "XMRig package to use.";
+        description = lib.mdDoc "XMRig package to use.";
       };
 
       settings = mkOption {
@@ -42,9 +42,9 @@ with lib;
             ]
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           XMRig configuration. Refer to
-          <link xlink:href="https://xmrig.com/docs/miner/config"/>
+          <https://xmrig.com/docs/miner/config>
           for details on supported values.
         '';
       };
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index a557e742b7cf..109415a20ee6 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -66,24 +66,22 @@ let
 in {
   options = {
     services.zoneminder = with lib; {
-      enable = lib.mkEnableOption ''
+      enable = lib.mkEnableOption (lib.mdDoc ''
         ZoneMinder
-        </para><para>
+
         If you intend to run the database locally, you should set
         `config.services.zoneminder.database.createLocally` to true. Otherwise,
         when set to `false` (the default), you will have to create the database
         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.
-          </para>
-          <para>
 
           Set it to `none` if you want to configure it yourself. PRs are welcome
           for support for other web servers.
@@ -93,15 +91,15 @@ in {
       hostname = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           The hostname on which to listen.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8095;
-        description = ''
+        description = lib.mdDoc ''
           The port on which to listen.
         '';
       };
@@ -109,7 +107,7 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open the firewall port(s).
         '';
       };
@@ -118,7 +116,7 @@ in {
         createLocally = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Create the database and database user locally.
           '';
         };
@@ -126,7 +124,7 @@ in {
         host = mkOption {
           type = types.str;
           default = "localhost";
-          description = ''
+          description = lib.mdDoc ''
             Hostname hosting the database.
           '';
         };
@@ -134,7 +132,7 @@ in {
         name = mkOption {
           type = types.str;
           default = "zm";
-          description = ''
+          description = lib.mdDoc ''
             Name of database.
           '';
         };
@@ -142,7 +140,7 @@ in {
         username = mkOption {
           type = types.str;
           default = "zmuser";
-          description = ''
+          description = lib.mdDoc ''
             Username for accessing the database.
           '';
         };
@@ -150,9 +148,9 @@ in {
         password = mkOption {
           type = types.str;
           default = "zmpass";
-          description = ''
+          description = lib.mdDoc ''
             Username for accessing the database.
-            Not used if <literal>createLocally</literal> is set.
+            Not used if `createLocally` is set.
           '';
         };
       };
@@ -160,7 +158,7 @@ in {
       cameras = mkOption {
         type = types.int;
         default = 1;
-        description = ''
+        description = lib.mdDoc ''
           Set this to the number of cameras you expect to support.
         '';
       };
@@ -169,7 +167,7 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "/storage/tank";
-        description = ''
+        description = lib.mdDoc ''
           ZoneMinder can generate quite a lot of data, so in case you don't want
           to use the default ${defaultDir}, you can override the path here.
         '';
@@ -178,7 +176,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Additional configuration added verbatim to the configuration file.
         '';
       };
diff --git a/nixos/modules/services/misc/zookeeper.nix b/nixos/modules/services/misc/zookeeper.nix
index 3809a93a61e1..fb51be698e72 100644
--- a/nixos/modules/services/misc/zookeeper.nix
+++ b/nixos/modules/services/misc/zookeeper.nix
@@ -24,26 +24,22 @@ let
 in {
 
   options.services.zookeeper = {
-    enable = mkOption {
-      description = "Whether to enable Zookeeper.";
-      default = false;
-      type = types.bool;
-    };
+    enable = mkEnableOption (lib.mdDoc "Zookeeper");
 
     port = mkOption {
-      description = "Zookeeper Client port.";
+      description = lib.mdDoc "Zookeeper Client port.";
       default = 2181;
-      type = types.int;
+      type = types.port;
     };
 
     id = mkOption {
-      description = "Zookeeper ID.";
+      description = lib.mdDoc "Zookeeper ID.";
       default = 0;
       type = types.int;
     };
 
     purgeInterval = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The time interval in hours for which the purge task has to be triggered. Set to a positive integer (1 and above) to enable the auto purging.
       '';
       default = 1;
@@ -51,7 +47,7 @@ in {
     };
 
     extraConf = mkOption {
-      description = "Extra configuration for Zookeeper.";
+      description = lib.mdDoc "Extra configuration for Zookeeper.";
       type = types.lines;
       default = ''
         initLimit=5
@@ -61,7 +57,7 @@ in {
     };
 
     servers = mkOption {
-      description = "All Zookeeper Servers.";
+      description = lib.mdDoc "All Zookeeper Servers.";
       default = "";
       type = types.lines;
       example = ''
@@ -72,7 +68,7 @@ in {
     };
 
     logging = mkOption {
-      description = "Zookeeper logging configuration.";
+      description = lib.mdDoc "Zookeeper logging configuration.";
       default = ''
         zookeeper.root.logger=INFO, CONSOLE
         log4j.rootLogger=INFO, CONSOLE
@@ -87,13 +83,13 @@ in {
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/zookeeper";
-      description = ''
+      description = lib.mdDoc ''
         Data directory for Zookeeper
       '';
     };
 
     extraCmdLineOptions = mkOption {
-      description = "Extra command line options for the Zookeeper launcher.";
+      description = lib.mdDoc "Extra command line options for the Zookeeper launcher.";
       default = [ "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ];
       type = types.listOf types.str;
       example = [ "-Djava.net.preferIPv4Stack=true" "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ];
@@ -102,18 +98,25 @@ in {
     preferIPv4 = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Add the -Djava.net.preferIPv4Stack=true flag to the Zookeeper server.
       '';
     };
 
     package = mkOption {
-      description = "The zookeeper package to use";
+      description = lib.mdDoc "The zookeeper package to use";
       default = pkgs.zookeeper;
       defaultText = literalExpression "pkgs.zookeeper";
       type = types.package;
     };
 
+    jre = mkOption {
+      description = lib.mdDoc "The JRE with which to run Zookeeper";
+      default = cfg.package.jre;
+      defaultText = literalExpression "pkgs.zookeeper.jre";
+      example = literalExpression "pkgs.jre";
+      type = types.package;
+    };
   };
 
 
@@ -131,7 +134,7 @@ in {
       after = [ "network.target" ];
       serviceConfig = {
         ExecStart = ''
-          ${pkgs.jre}/bin/java \
+          ${cfg.jre}/bin/java \
             -cp "${cfg.package}/lib/*:${configDir}" \
             ${escapeShellArgs cfg.extraCmdLineOptions} \
             -Dzookeeper.datadir.autocreate=false \
diff --git a/nixos/modules/services/monitoring/alerta.nix b/nixos/modules/services/monitoring/alerta.nix
index a73d94001f71..6c7ebec4191c 100644
--- a/nixos/modules/services/monitoring/alerta.nix
+++ b/nixos/modules/services/monitoring/alerta.nix
@@ -21,58 +21,58 @@ let
 in
 {
   options.services.alerta = {
-    enable = mkEnableOption "alerta";
+    enable = mkEnableOption (lib.mdDoc "alerta");
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 5000;
-      description = "Port of Alerta";
+      description = lib.mdDoc "Port of Alerta";
     };
 
     bind = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = "Address to bind to. The default is to bind to all addresses";
+      description = lib.mdDoc "Address to bind to. The default is to bind to all addresses";
     };
 
     logDir = mkOption {
       type = types.path;
-      description = "Location where the logfiles are stored";
+      description = lib.mdDoc "Location where the logfiles are stored";
       default = "/var/log/alerta";
     };
 
     databaseUrl = mkOption {
       type = types.str;
-      description = "URL of the MongoDB or PostgreSQL database to connect to";
+      description = lib.mdDoc "URL of the MongoDB or PostgreSQL database to connect to";
       default = "mongodb://localhost";
     };
 
     databaseName = mkOption {
       type = types.str;
-      description = "Name of the database instance to connect to";
+      description = lib.mdDoc "Name of the database instance to connect to";
       default = "monitoring";
     };
 
     corsOrigins = mkOption {
       type = types.listOf types.str;
-      description = "List of URLs that can access the API for Cross-Origin Resource Sharing (CORS)";
+      description = lib.mdDoc "List of URLs that can access the API for Cross-Origin Resource Sharing (CORS)";
       default = [ "http://localhost" "http://localhost:5000" ];
     };
 
     authenticationRequired = mkOption {
       type = types.bool;
-      description = "Whether users must authenticate when using the web UI or command-line tool";
+      description = lib.mdDoc "Whether users must authenticate when using the web UI or command-line tool";
       default = false;
     };
 
     signupEnabled = mkOption {
       type = types.bool;
-      description = "Whether to prevent sign-up of new users via the web UI";
+      description = lib.mdDoc "Whether to prevent sign-up of new users via the web UI";
       default = true;
     };
 
     extraConfig = mkOption {
-      description = "These lines go into alertad.conf verbatim.";
+      description = lib.mdDoc "These lines go into alertad.conf verbatim.";
       default = "";
       type = types.lines;
     };
diff --git a/nixos/modules/services/monitoring/apcupsd.nix b/nixos/modules/services/monitoring/apcupsd.nix
index 1dccbc93edf8..d4216b44cdc8 100644
--- a/nixos/modules/services/monitoring/apcupsd.nix
+++ b/nixos/modules/services/monitoring/apcupsd.nix
@@ -75,7 +75,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the APC UPS daemon. apcupsd monitors your UPS and
           permits orderly shutdown of your computer in the event of a power
           failure. User manual: http://www.apcupsd.com/manual/manual.html.
@@ -92,7 +92,7 @@ in
           MINUTES 5
         '';
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Contents of the runtime configuration file, apcupsd.conf. The default
           settings makes apcupsd autodetect USB UPSes, limit network access to
           localhost and shutdown the system when the battery level is below 50
@@ -107,7 +107,7 @@ in
           doshutdown = "# shell commands to notify that the computer is shutting down";
         };
         type = types.attrsOf types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this option names an apcupsd event and the string
           value it contains will be executed in a shell, in response to that
           event (prior to the default action). See "man apccontrol" for the
diff --git a/nixos/modules/services/monitoring/arbtt.nix b/nixos/modules/services/monitoring/arbtt.nix
index 94eead220aed..f07ecc5d5dd0 100644
--- a/nixos/modules/services/monitoring/arbtt.nix
+++ b/nixos/modules/services/monitoring/arbtt.nix
@@ -7,19 +7,13 @@ let
 in {
   options = {
     services.arbtt = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable the arbtt statistics capture service.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "Arbtt statistics capture service");
 
       package = mkOption {
         type = types.package;
         default = pkgs.haskellPackages.arbtt;
         defaultText = literalExpression "pkgs.haskellPackages.arbtt";
-        description = ''
+        description = lib.mdDoc ''
           The package to use for the arbtt binaries.
         '';
       };
@@ -28,7 +22,7 @@ in {
         type = types.str;
         default = "%h/.arbtt/capture.log";
         example = "/home/username/.arbtt-capture.log";
-        description = ''
+        description = lib.mdDoc ''
           The log file for captured samples.
         '';
       };
@@ -37,7 +31,7 @@ in {
         type = types.int;
         default = 60;
         example = 120;
-        description = ''
+        description = lib.mdDoc ''
           The sampling interval in seconds.
         '';
       };
diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix
index 4b278b9c200b..dc75fda6ed8a 100644
--- a/nixos/modules/services/monitoring/bosun.nix
+++ b/nixos/modules/services/monitoring/bosun.nix
@@ -22,19 +22,13 @@ in {
 
     services.bosun = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to run bosun.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "bosun");
 
       package = mkOption {
         type = types.package;
         default = pkgs.bosun;
         defaultText = literalExpression "pkgs.bosun";
-        description = ''
+        description = lib.mdDoc ''
           bosun binary to use.
         '';
       };
@@ -42,7 +36,7 @@ in {
       user = mkOption {
         type = types.str;
         default = "bosun";
-        description = ''
+        description = lib.mdDoc ''
           User account under which bosun runs.
         '';
       };
@@ -50,7 +44,7 @@ in {
       group = mkOption {
         type = types.str;
         default = "bosun";
-        description = ''
+        description = lib.mdDoc ''
           Group account under which bosun runs.
         '';
       };
@@ -58,7 +52,7 @@ in {
       opentsdbHost = mkOption {
         type = types.nullOr types.str;
         default = "localhost:4242";
-        description = ''
+        description = lib.mdDoc ''
           Host and port of the OpenTSDB database that stores bosun data.
           To disable opentsdb you can pass null as parameter.
         '';
@@ -68,7 +62,7 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "localhost:8086";
-        description = ''
+        description = lib.mdDoc ''
            Host and port of the influxdb database.
         '';
       };
@@ -76,7 +70,7 @@ in {
       listenAddress = mkOption {
         type = types.str;
         default = ":8070";
-        description = ''
+        description = lib.mdDoc ''
           The host address and port that bosun's web interface will listen on.
         '';
       };
@@ -84,7 +78,7 @@ in {
       stateFile = mkOption {
         type = types.path;
         default = "/var/lib/bosun/bosun.state";
-        description = ''
+        description = lib.mdDoc ''
           Path to bosun's state file.
         '';
       };
@@ -92,7 +86,7 @@ in {
       ledisDir = mkOption {
         type = types.path;
         default = "/var/lib/bosun/ledis_data";
-        description = ''
+        description = lib.mdDoc ''
           Path to bosun's ledis data dir
         '';
       };
@@ -100,7 +94,7 @@ in {
       checkFrequency = mkOption {
         type = types.str;
         default = "5m";
-        description = ''
+        description = lib.mdDoc ''
           Bosun's check frequency
         '';
       };
@@ -108,7 +102,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options for Bosun. You should describe your
           desired templates, alerts, macros, etc through this configuration
           option.
diff --git a/nixos/modules/services/monitoring/cadvisor.nix b/nixos/modules/services/monitoring/cadvisor.nix
index dfbf07efcaea..a8fba4e6e8ce 100644
--- a/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixos/modules/services/monitoring/cadvisor.nix
@@ -8,90 +8,86 @@ let
 in {
   options = {
     services.cadvisor = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Whether to enable cadvisor service.";
-      };
+      enable = mkEnableOption (lib.mdDoc "Cadvisor service");
 
       listenAddress = mkOption {
         default = "127.0.0.1";
         type = types.str;
-        description = "Cadvisor listening host";
+        description = lib.mdDoc "Cadvisor listening host";
       };
 
       port = mkOption {
         default = 8080;
-        type = types.int;
-        description = "Cadvisor listening port";
+        type = types.port;
+        description = lib.mdDoc "Cadvisor listening port";
       };
 
       storageDriver = mkOption {
         default = null;
         type = types.nullOr types.str;
         example = "influxdb";
-        description = "Cadvisor storage driver.";
+        description = lib.mdDoc "Cadvisor storage driver.";
       };
 
       storageDriverHost = mkOption {
         default = "localhost:8086";
         type = types.str;
-        description = "Cadvisor storage driver host.";
+        description = lib.mdDoc "Cadvisor storage driver host.";
       };
 
       storageDriverDb = mkOption {
         default = "root";
         type = types.str;
-        description = "Cadvisord storage driver database name.";
+        description = lib.mdDoc "Cadvisord storage driver database name.";
       };
 
       storageDriverUser = mkOption {
         default = "root";
         type = types.str;
-        description = "Cadvisor storage driver username.";
+        description = lib.mdDoc "Cadvisor storage driver username.";
       };
 
       storageDriverPassword = mkOption {
         default = "root";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Cadvisor storage driver password.
 
           Warning: this password is stored in the world-readable Nix store. It's
-          recommended to use the <option>storageDriverPasswordFile</option> option
+          recommended to use the {option}`storageDriverPasswordFile` option
           since that gives you control over the security of the password.
-          <option>storageDriverPasswordFile</option> also takes precedence over <option>storageDriverPassword</option>.
+          {option}`storageDriverPasswordFile` also takes precedence over {option}`storageDriverPassword`.
         '';
       };
 
       storageDriverPasswordFile = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           File that contains the cadvisor storage driver password.
 
-          <option>storageDriverPasswordFile</option> takes precedence over <option>storageDriverPassword</option>
+          {option}`storageDriverPasswordFile` takes precedence over {option}`storageDriverPassword`
 
-          Warning: when <option>storageDriverPassword</option> is non-empty this defaults to a file in the
-          world-readable Nix store that contains the value of <option>storageDriverPassword</option>.
+          Warning: when {option}`storageDriverPassword` is non-empty this defaults to a file in the
+          world-readable Nix store that contains the value of {option}`storageDriverPassword`.
 
           It's recommended to override this with a path not in the Nix store.
-          Tip: use <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'>nixops key management</link>
+          Tip: use [nixops key management](https://nixos.org/nixops/manual/#idm140737318306400)
         '';
       };
 
       storageDriverSecure = mkOption {
         default = false;
         type = types.bool;
-        description = "Cadvisor storage driver, enable secure communication.";
+        description = lib.mdDoc "Cadvisor storage driver, enable secure communication.";
       };
 
       extraOptions = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Additional cadvisor options.
 
-          See <link xlink:href='https://github.com/google/cadvisor/blob/master/docs/runtime_options.md'/> for available options.
+          See <https://github.com/google/cadvisor/blob/master/docs/runtime_options.md> for available options.
         '';
       };
     };
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 1b9af5857560..5d525995c67a 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -29,11 +29,11 @@ let
 
 in {
   options.services.collectd = with types; {
-    enable = mkEnableOption "collectd agent";
+    enable = mkEnableOption (lib.mdDoc "collectd agent");
 
     validateConfig = mkOption {
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Validate the syntax of collectd configuration file at build time.
         Disable this if you use the Include directive on files unavailable in
         the build sandbox, or when cross-compiling.
@@ -44,7 +44,7 @@ in {
     package = mkOption {
       default = pkgs.collectd;
       defaultText = literalExpression "pkgs.collectd";
-      description = ''
+      description = lib.mdDoc ''
         Which collectd package to use.
       '';
       type = types.package;
@@ -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;
@@ -60,7 +60,7 @@ in {
 
     user = mkOption {
       default = "collectd";
-      description = ''
+      description = lib.mdDoc ''
         User under which to run collectd.
       '';
       type = nullOr str;
@@ -68,7 +68,7 @@ in {
 
     dataDir = mkOption {
       default = "/var/lib/collectd";
-      description = ''
+      description = lib.mdDoc ''
         Data directory for collectd agent.
       '';
       type = path;
@@ -76,7 +76,7 @@ in {
 
     autoLoadPlugin = mkOption {
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable plugin autoloading.
       '';
       type = bool;
@@ -84,7 +84,7 @@ in {
 
     include = mkOption {
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Additional paths to load config from.
       '';
       type = listOf str;
@@ -93,7 +93,7 @@ in {
     plugins = mkOption {
       default = {};
       example = { cpu = ""; memory = ""; network = "Server 192.168.1.1 25826"; };
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of plugin names to plugin config segments
       '';
       type = attrsOf lines;
@@ -101,7 +101,7 @@ in {
 
     extraConfig = mkOption {
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra configuration for collectd. Use mkBefore to add lines before the
         default config, and mkAfter to add them below.
       '';
diff --git a/nixos/modules/services/monitoring/das_watchdog.nix b/nixos/modules/services/monitoring/das_watchdog.nix
index 88ca3a9227d2..fd420b0c8a06 100644
--- a/nixos/modules/services/monitoring/das_watchdog.nix
+++ b/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/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix
index 6d9d1ef973a4..15deef18b60f 100644
--- a/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixos/modules/services/monitoring/datadog-agent.nix
@@ -49,18 +49,12 @@ let
   };
 in {
   options.services.datadog-agent = {
-    enable = mkOption {
-      description = ''
-        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
@@ -70,7 +64,7 @@ in {
     };
 
     apiKeyFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Path to a file containing the Datadog API key to associate the
         agent with your account.
       '';
@@ -79,7 +73,7 @@ in {
     };
 
     ddUrl = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Custom dd_url to configure the agent with. Useful if traffic to datadog
         needs to go through a proxy.
         Don't use this to point to another datadog site (EU) - use site instead.
@@ -90,7 +84,7 @@ in {
     };
 
     site = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The datadog site to point the agent towards.
         Set to datadoghq.eu to point it to their EU site.
       '';
@@ -100,21 +94,21 @@ in {
     };
 
     tags = mkOption {
-      description = "The tags to mark this Datadog agent";
+      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 = "The hostname to show in the Datadog dashboard (optional)";
+      description = lib.mdDoc "The hostname to show in the Datadog dashboard (optional)";
       default = null;
       example = "mymachine.mydomain";
       type = types.nullOr types.str;
     };
 
     logLevel = mkOption {
-      description = "Logging verbosity.";
+      description = lib.mdDoc "Logging verbosity.";
       default = null;
       type = types.nullOr (types.enum ["DEBUG" "INFO" "WARN" "ERROR"]);
     };
@@ -123,7 +117,7 @@ in {
       default = {};
       type    = types.attrs;
 
-      description = ''
+      description = lib.mdDoc ''
         Extra integrations from the Datadog core-integrations
         repository that should be built and included.
 
@@ -145,14 +139,14 @@ in {
     extraConfig = mkOption {
       default = {};
       type = types.attrs;
-      description = ''
+      description = lib.mdDoc ''
         Extra configuration options that will be merged into the
-        main config file <filename>datadog.yaml</filename>.
+        main config file {file}`datadog.yaml`.
       '';
      };
 
     enableLiveProcessCollection = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the live process collection agent.
       '';
       default = false;
@@ -160,7 +154,7 @@ in {
     };
 
     enableTraceAgent = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the trace agent.
       '';
       default = false;
@@ -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`.
@@ -207,7 +201,7 @@ in {
     };
 
     diskCheck = mkOption {
-      description = "Disk check config";
+      description = lib.mdDoc "Disk check config";
       type = types.attrs;
       default = {
         init_config = {};
@@ -216,7 +210,7 @@ in {
     };
 
     networkCheck = mkOption {
-      description = "Network check config";
+      description = lib.mdDoc "Network check config";
       type = types.attrs;
       default = {
         init_config = {};
diff --git a/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix b/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix
deleted file mode 100644
index 045128197421..000000000000
--- a/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/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
deleted file mode 100644
index a290dae8d4b9..000000000000
--- a/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 = ''
-        Whether to enable the dd-agent v5 monitoring service.
-        For datadog-agent v6, see <option>services.datadog-agent.enable</option>.
-      '';
-      default = false;
-      type = types.bool;
-    };
-
-    api_key = mkOption {
-      description = ''
-        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</option> module instead.
-      '';
-      example = "ae0aa6a8f08efa988ba0a17578f009ab";
-      type = types.str;
-    };
-
-    tags = mkOption {
-      description = "The tags to mark this Datadog agent";
-      example = [ "test" "service" ];
-      default = null;
-      type = types.nullOr (types.listOf types.str);
-    };
-
-    hostname = mkOption {
-      description = "The hostname to show in the Datadog dashboard (optional)";
-      default = null;
-      example = "mymachine.mydomain";
-      type = types.nullOr types.str;
-    };
-
-    postgresqlConfig = mkOption {
-      description = "Datadog PostgreSQL integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    nginxConfig = mkOption {
-      description = "Datadog nginx integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    mongoConfig = mkOption {
-      description = "MongoDB integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    jmxConfig = mkOption {
-      description = "JMX integration configuration";
-      default = null;
-      type = types.nullOr types.lines;
-    };
-
-    processConfig = mkOption {
-      description = ''
-        Process integration configuration
-        See <link xlink:href="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/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults b/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults
deleted file mode 100755
index 76724173171a..000000000000
--- a/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/nixos/modules/services/monitoring/do-agent.nix b/nixos/modules/services/monitoring/do-agent.nix
index 4dfb6236727b..c1788c640c23 100644
--- a/nixos/modules/services/monitoring/do-agent.nix
+++ b/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/nixos/modules/services/monitoring/fusion-inventory.nix b/nixos/modules/services/monitoring/fusion-inventory.nix
index 9b65c76ce02e..7b28e8de1229 100644
--- a/nixos/modules/services/monitoring/fusion-inventory.nix
+++ b/nixos/modules/services/monitoring/fusion-inventory.nix
@@ -22,11 +22,11 @@ in {
 
     services.fusionInventory = {
 
-      enable = mkEnableOption "Fusion Inventory Agent";
+      enable = mkEnableOption (lib.mdDoc "Fusion Inventory Agent");
 
       servers = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The urls of the OCS/GLPI servers to connect to.
         '';
       };
@@ -34,7 +34,7 @@ in {
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Configuration that is injected verbatim into the configuration file.
         '';
       };
diff --git a/nixos/modules/services/monitoring/grafana-agent.nix b/nixos/modules/services/monitoring/grafana-agent.nix
new file mode 100644
index 000000000000..270d888afb78
--- /dev/null
+++ b/nixos/modules/services/monitoring/grafana-agent.nix
@@ -0,0 +1,159 @@
+{ lib, pkgs, config, generators, ... }:
+with lib;
+let
+  cfg = config.services.grafana-agent;
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "grafana-agent.yaml" cfg.settings;
+in
+{
+  meta = {
+    maintainers = with maintainers; [ flokli zimbatm ];
+  };
+
+  options.services.grafana-agent = {
+    enable = mkEnableOption (lib.mdDoc "grafana-agent");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.grafana-agent;
+      defaultText = lib.literalExpression "pkgs.grafana-agent";
+      description = lib.mdDoc "The grafana-agent package to use.";
+    };
+
+    credentials = mkOption {
+      description = lib.mdDoc ''
+        Credentials to load at service startup. Keys that are UPPER_SNAKE will be loaded as env vars. Values are absolute paths to the credentials.
+      '';
+      type = types.attrsOf types.str;
+      default = { };
+
+      example = {
+        logs_remote_write_password = "/run/keys/grafana_agent_logs_remote_write_password";
+        LOGS_REMOTE_WRITE_URL = "/run/keys/grafana_agent_logs_remote_write_url";
+        LOGS_REMOTE_WRITE_USERNAME = "/run/keys/grafana_agent_logs_remote_write_username";
+        metrics_remote_write_password = "/run/keys/grafana_agent_metrics_remote_write_password";
+        METRICS_REMOTE_WRITE_URL = "/run/keys/grafana_agent_metrics_remote_write_url";
+        METRICS_REMOTE_WRITE_USERNAME = "/run/keys/grafana_agent_metrics_remote_write_username";
+      };
+    };
+
+    settings = mkOption {
+      description = lib.mdDoc ''
+        Configuration for `grafana-agent`.
+
+        See https://grafana.com/docs/agent/latest/configuration/
+      '';
+
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+      };
+
+      default = { };
+      defaultText = lib.literalExpression ''
+        {
+          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;
+          };
+        }
+      '';
+      example = {
+        metrics.global.remote_write = [{
+          url = "\${METRICS_REMOTE_WRITE_URL}";
+          basic_auth.username = "\${METRICS_REMOTE_WRITE_USERNAME}";
+          basic_auth.password_file = "\${CREDENTIALS_DIRECTORY}/metrics_remote_write_password";
+        }];
+        logs.configs = [{
+          name = "default";
+          scrape_configs = [
+            {
+              job_name = "journal";
+              journal = {
+                max_age = "12h";
+                labels.job = "systemd-journal";
+              };
+              relabel_configs = [
+                {
+                  source_labels = [ "__journal__systemd_unit" ];
+                  target_label = "systemd_unit";
+                }
+                {
+                  source_labels = [ "__journal__hostname" ];
+                  target_label = "nodename";
+                }
+                {
+                  source_labels = [ "__journal_syslog_identifier" ];
+                  target_label = "syslog_identifier";
+                }
+              ];
+            }
+          ];
+          positions.filename = "\${STATE_DIRECTORY}/loki_positions.yaml";
+          clients = [{
+            url = "\${LOGS_REMOTE_WRITE_URL}";
+            basic_auth.username = "\${LOGS_REMOTE_WRITE_USERNAME}";
+            basic_auth.password_file = "\${CREDENTIALS_DIRECTORY}/logs_remote_write_password";
+          }];
+        }];
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.grafana-agent.settings = {
+      # keep this in sync with config.services.grafana-agent.settings.defaultText.
+      metrics = {
+        wal_directory = mkDefault "\${STATE_DIRECTORY}";
+        global.scrape_interval = mkDefault "5s";
+      };
+      integrations = {
+        agent.enabled = mkDefault true;
+        agent.scrape_integration = mkDefault true;
+        node_exporter.enabled = mkDefault true;
+        replace_instance_label = mkDefault true;
+      };
+    };
+
+    systemd.services.grafana-agent = {
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        set -euo pipefail
+        shopt -u nullglob
+
+        # Load all credentials into env if they are in UPPER_SNAKE form.
+        if [[ -n "''${CREDENTIALS_DIRECTORY:-}" ]]; then
+          for file in "$CREDENTIALS_DIRECTORY"/*; do
+            key=$(basename "$file")
+            if [[ $key =~ ^[A-Z0-9_]+$ ]]; then
+              echo "Environ $key"
+              export "$key=$(< "$file")"
+            fi
+          done
+        fi
+
+        # 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}
+      '';
+      serviceConfig = {
+        Restart = "always";
+        DynamicUser = true;
+        RestartSec = 2;
+        SupplementaryGroups = [
+          # allow to read the systemd journal for loki log forwarding
+          "systemd-journal"
+        ];
+        StateDirectory = "grafana-agent";
+        LoadCredential = lib.mapAttrsToList (key: value: "${key}:${value}") cfg.credentials;
+        Type = "simple";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/grafana-image-renderer.nix b/nixos/modules/services/monitoring/grafana-image-renderer.nix
index b8b95d846c6a..60f6e84c63c7 100644
--- a/nixos/modules/services/monitoring/grafana-image-renderer.nix
+++ b/nixos/modules/services/monitoring/grafana-image-renderer.nix
@@ -10,18 +10,18 @@ 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;
-      description = ''
+      description = lib.mdDoc ''
         The chromium to use for image rendering.
       '';
     };
 
-    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 {
@@ -32,15 +32,15 @@ in {
             port = mkOption {
               type = types.port;
               default = 8081;
-              description = ''
+              description = lib.mdDoc ''
                 The TCP port to use for the rendering server.
               '';
             };
             logging.level = mkOption {
               type = types.enum [ "error" "warning" "info" "debug" ];
               default = "info";
-              description = ''
-                The log-level of the <filename>grafana-image-renderer.service</filename>-unit.
+              description = lib.mdDoc ''
+                The log-level of the {file}`grafana-image-renderer.service`-unit.
               '';
             };
           };
@@ -48,39 +48,37 @@ in {
             width = mkOption {
               default = 1000;
               type = types.ints.positive;
-              description = ''
+              description = lib.mdDoc ''
                 Width of the PNG used to display the alerting graph.
               '';
             };
             height = mkOption {
               default = 500;
               type = types.ints.positive;
-              description = ''
+              description = lib.mdDoc ''
                 Height of the PNG used to display the alerting graph.
               '';
             };
             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 {
+      url = "http://localhost:${toString cfg.settings.service.port}/render";
+      callback_url = "http://localhost:${toString config.services.grafana.port}";
     };
 
     services.grafana-image-renderer.chromium = mkDefault pkgs.chromium;
diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
index e40d78f538fa..eac304d63aa1 100644
--- a/nixos/modules/services/monitoring/grafana-reporter.nix
+++ b/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -7,40 +7,40 @@ let
 
 in {
   options.services.grafana_reporter = {
-    enable = mkEnableOption "grafana_reporter";
+    enable = mkEnableOption (lib.mdDoc "grafana_reporter");
 
     grafana = {
       protocol = mkOption {
-        description = "Grafana protocol.";
+        description = lib.mdDoc "Grafana protocol.";
         default = "http";
         type = types.enum ["http" "https"];
       };
       addr = mkOption {
-        description = "Grafana address.";
+        description = lib.mdDoc "Grafana address.";
         default = "127.0.0.1";
         type = types.str;
       };
       port = mkOption {
-        description = "Grafana port.";
+        description = lib.mdDoc "Grafana port.";
         default = 3000;
-        type = types.int;
+        type = types.port;
       };
 
     };
     addr = mkOption {
-      description = "Listening address.";
+      description = lib.mdDoc "Listening address.";
       default = "127.0.0.1";
       type = types.str;
     };
 
     port = mkOption {
-      description = "Listening port.";
+      description = lib.mdDoc "Listening port.";
       default = 8686;
-      type = types.int;
+      type = types.port;
     };
 
     templateDir = mkOption {
-      description = "Optional template directory to use custom tex templates";
+      description = lib.mdDoc "Optional template directory to use custom tex templates";
       default = pkgs.grafana_reporter;
       defaultText = literalExpression "pkgs.grafana_reporter";
       type = types.either types.str types.path;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index b959379d331a..9a9a0ab75532 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -5,220 +5,165 @@ 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_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_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_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;
-  };
+  useMysql = cfg.settings.database.type == "mysql";
+  usePostgresql = cfg.settings.database.type == "postgres";
 
-  datasourceFile = pkgs.writeText "datasource.yaml" (builtins.toJSON datasourceConfiguration);
+  settingsFormatIni = pkgs.formats.ini {};
+  configFile = settingsFormatIni.generate "config.ini" cfg.settings;
 
-  dashboardConfiguration = {
-    apiVersion = 1;
-    providers = cfg.provision.dashboards;
-  };
+  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} = [];
+          });
 
-  dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration);
+  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);
+  notifierFileOrDir = 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
+  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 = "dashbaord"; }}
+    ${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;
+
+    imports = [
+      (mkRemovedOptionModule [ "password" ] ''
+        `services.grafana.provision.datasources.settings.datasources.<name>.password` has been removed
+        in Grafana 9. Use `secureJsonData` instead.
+      '')
+      (mkRemovedOptionModule [ "basicAuthPassword" ] ''
+        `services.grafana.provision.datasources.settings.datasources.<name>.basicAuthPassword` has been removed
+        in Grafana 9. Use `secureJsonData` instead.
+      '')
+    ];
+
     options = {
       name = mkOption {
         type = types.str;
-        description = "Name of the datasource. Required.";
+        description = lib.mdDoc "Name of the datasource. Required.";
       };
       type = mkOption {
         type = types.str;
-        description = "Datasource type. Required.";
+        description = lib.mdDoc "Datasource type. Required.";
       };
       access = mkOption {
         type = types.enum ["proxy" "direct"];
         default = "proxy";
-        description = "Access mode. proxy or direct (Server or Browser in the UI). Required.";
-      };
-      orgId = mkOption {
-        type = types.int;
-        default = 1;
-        description = "Org id. will default to orgId 1 if not specified.";
-      };
-      url = mkOption {
-        type = types.str;
-        description = "Url of the datasource.";
+        description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required.";
       };
-      password = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Database password, if used.";
-      };
-      user = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Database user, if used.";
-      };
-      database = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Database name, if used.";
-      };
-      basicAuth = mkOption {
-        type = types.nullOr types.bool;
-        default = null;
-        description = "Enable/disable basic auth.";
-      };
-      basicAuthUser = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = "Basic auth username.";
-      };
-      basicAuthPassword = mkOption {
+      uid = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Basic auth password.";
+        description = lib.mdDoc "Custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically.";
       };
-      withCredentials = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Enable/disable with credentials headers.";
+      url = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc "Url of the datasource.";
       };
-      isDefault = mkOption {
+      editable = mkOption {
         type = types.bool;
         default = false;
-        description = "Mark as default datasource. Max one per org.";
-      };
-      jsonData = mkOption {
-        type = types.nullOr types.attrs;
-        default = null;
-        description = "Datasource specific configuration.";
+        description = lib.mdDoc "Allow users to edit datasources from the UI.";
       };
       secureJsonData = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = "Datasource specific secure configuration.";
-      };
-      version = mkOption {
-        type = types.int;
-        default = 1;
-        description = "Version.";
-      };
-      editable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "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 = "Provider name.";
-      };
-      orgId = mkOption {
-        type = types.int;
-        default = 1;
-        description = "Organization ID.";
-      };
-      folder = mkOption {
-        type = types.str;
-        default = "";
-        description = "Add dashboards to the specified folder.";
+        description = lib.mdDoc "A unique provider name.";
       };
       type = mkOption {
         type = types.str;
         default = "file";
-        description = "Dashboard provider type.";
+        description = lib.mdDoc "Dashboard provider type.";
       };
-      disableDeletion = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Disable deletion when JSON file is removed.";
-      };
-      updateIntervalSeconds = mkOption {
-        type = types.int;
-        default = 10;
-        description = "How often Grafana will scan for changed dashboards.";
-      };
-      options = {
-        path = mkOption {
-          type = types.path;
-          description = "Path grafana will watch for dashboards.";
-        };
-        foldersFromFilesStructure = mkOption {
-          type = types.bool;
-          default = false;
-          description = "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.";
       };
     };
   };
@@ -228,128 +173,141 @@ let
       name = mkOption {
         type = types.str;
         default = "default";
-        description = "Notifier name.";
+        description = lib.mdDoc "Notifier name.";
       };
       type = mkOption {
         type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"];
-        description = "Notifier type.";
+        description = lib.mdDoc "Notifier type.";
       };
       uid = mkOption {
         type = types.str;
-        description = "Unique notifier identifier.";
+        description = lib.mdDoc "Unique notifier identifier.";
       };
       org_id = mkOption {
         type = types.int;
         default = 1;
-        description = "Organization ID.";
+        description = lib.mdDoc "Organization ID.";
       };
       org_name = mkOption {
         type = types.str;
         default = "Main Org.";
-        description = "Organization name.";
+        description = lib.mdDoc "Organization name.";
       };
       is_default = mkOption {
         type = types.bool;
-        description = "Is the default notifier.";
+        description = lib.mdDoc "Is the default notifier.";
         default = false;
       };
       send_reminder = mkOption {
         type = types.bool;
         default = true;
-        description = "Should the notifier be sent reminder notifications while alerts continue to fire.";
+        description = lib.mdDoc "Should the notifier be sent reminder notifications while alerts continue to fire.";
       };
       frequency = mkOption {
         type = types.str;
         default = "5m";
-        description = "How frequently should the notifier be sent reminders.";
+        description = lib.mdDoc "How frequently should the notifier be sent reminders.";
       };
       disable_resolve_message = mkOption {
         type = types.bool;
         default = false;
-        description = "Turn off the message that sends when an alert returns to OK.";
+        description = lib.mdDoc "Turn off the message that sends when an alert returns to OK.";
       };
       settings = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = "Settings for the notifier type.";
+        description = lib.mdDoc "Settings for the notifier type.";
       };
       secure_settings = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = "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 = "Which protocol to listen.";
-      default = "http";
-      type = types.enum ["http" "https" "socket"];
-    };
-
-    addr = mkOption {
-      description = "Listening address.";
-      default = "127.0.0.1";
-      type = types.str;
-    };
-
-    port = mkOption {
-      description = "Listening port.";
-      default = 3000;
-      type = types.port;
-    };
+  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" ])
 
-    socket = mkOption {
-      description = "Listening socket.";
-      default = "/run/grafana/grafana.sock";
-      type = types.str;
-    };
-
-    domain = mkOption {
-      description = "The public facing domain name used to access grafana from a browser.";
-      default = "localhost";
-      type = types.str;
-    };
-
-    rootUrl = mkOption {
-      description = "Full public facing url.";
-      default = "%(protocol)s://%(domain)s:%(http_port)s/";
-      type = types.str;
-    };
-
-    certFile = mkOption {
-      description = "Cert file for ssl.";
-      default = "";
-      type = types.str;
-    };
+    (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.
+    '')
 
-    certKey = mkOption {
-      description = "Cert key for ssl.";
-      default = "";
-      type = types.str;
-    };
+    (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.")
+  ];
 
-    staticRootPath = mkOption {
-      description = "Root path for static assets.";
-      default = "${cfg.package}/share/grafana/public";
-      defaultText = literalExpression ''"''${package}/share/grafana/public"'';
-      type = types.str;
-    };
-
-    package = mkOption {
-      description = "Package to use.";
-      default = pkgs.grafana;
-      defaultText = literalExpression "pkgs.grafana";
-      type = types.package;
-    };
+  options.services.grafana = {
+    enable = mkEnableOption (lib.mdDoc "grafana");
 
     declarativePlugins = mkOption {
       type = with types; nullOr (listOf path);
       default = null;
-      description = "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed.";
+      description = lib.mdDoc "If non-null, then a list of packages containing Grafana plugins to install. If set, plugins cannot be manually installed.";
       example = literalExpression "with pkgs.grafanaPlugins; [ grafana-piechart-panel ]";
       # Make sure each plugin is added only once; otherwise building
       # the link farm fails, since the same path is added multiple
@@ -357,287 +315,939 @@ 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 = "Data directory.";
+      description = lib.mdDoc "Data directory.";
       default = "/var/lib/grafana";
       type = types.path;
     };
 
-    database = {
-      type = mkOption {
-        description = "Database type.";
-        default = "sqlite3";
-        type = types.enum ["mysql" "sqlite3" "postgres"];
-      };
+    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;
 
-      host = mkOption {
-        description = "Database host.";
-        default = "127.0.0.1:3306";
-        type = types.str;
-      };
+        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;
+            };
 
-      name = mkOption {
-        description = "Database name.";
-        default = "grafana";
-        type = types.str;
-      };
+            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;
+            };
+          };
 
-      user = mkOption {
-        description = "Database user.";
-        default = "root";
-        type = types.str;
-      };
+          server = {
+            protocol = mkOption {
+              description = lib.mdDoc "Which protocol to listen.";
+              default = "http";
+              type = types.enum ["http" "https" "h2" "socket"];
+            };
 
-      password = mkOption {
-        description = ''
-          Database password.
-          This option is mutual exclusive with the passwordFile option.
-        '';
-        default = "";
-        type = types.str;
-      };
+            http_addr = mkOption {
+              description = lib.mdDoc "Listening address.";
+              default = "";
+              type = types.str;
+            };
 
-      passwordFile = mkOption {
-        description = ''
-          File that containts the database password.
-          This option is mutual exclusive with the password option.
-        '';
-        default = null;
-        type = types.nullOr types.path;
-      };
+            http_port = mkOption {
+              description = lib.mdDoc "Listening port.";
+              default = 3000;
+              type = types.port;
+            };
 
-      path = mkOption {
-        description = "Database path.";
-        default = "${cfg.dataDir}/data/grafana.db";
-        defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"'';
-        type = types.path;
-      };
+            domain = mkOption {
+              description = lib.mdDoc "The public facing domain name used to access grafana from a browser.";
+              default = "localhost";
+              type = types.str;
+            };
 
-      connMaxLifetime = mkOption {
-        description = ''
-          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.
-        '';
-        default = "unlimited";
-        example = 14400;
-        type = types.either types.int (types.enum [ "unlimited" ]);
+            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;
+          };
+        };
       };
     };
 
     provision = {
-      enable = mkEnableOption "provision";
+      enable = mkEnableOption (lib.mdDoc "provision");
+
       datasources = mkOption {
-        description = "Grafana datasources configuration.";
-        default = [];
-        type = types.listOf grafanaTypes.datasourceConfig;
-        apply = x: map _filter x;
+        description = lib.mdDoc ''
+          Declaratively provision Grafana's datasources.
+        '';
+        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;
+                  apply = map (flip builtins.removeAttrs [ "password" "basicAuthPassword" ]);
+                };
+
+                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;
+                }];
+              }
+            '';
+          };
+
+          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;
+          };
+        };
       };
+
+
       dashboards = mkOption {
-        description = "Grafana dashboard configuration.";
-        default = [];
-        type = types.listOf grafanaTypes.dashboardConfig;
-        apply = x: map _filter x;
+        description = lib.mdDoc ''
+          Declaratively provision Grafana's dashboards.
+        '';
+        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";
+                }];
+              }
+            '';
+          };
+
+          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 = "Grafana notifier configuration.";
+        description = lib.mdDoc "Grafana notifier configuration.";
         default = [];
         type = types.listOf grafanaTypes.notifierConfig;
         apply = x: map _filter x;
       };
-    };
 
-    security = {
-      adminUser = mkOption {
-        description = "Default admin username.";
-        default = "admin";
-        type = types.str;
-      };
 
-      adminPassword = mkOption {
-        description = ''
-          Default admin password.
-          This option is mutual exclusive with the adminPasswordFile option.
-        '';
-        default = "admin";
-        type = types.str;
-      };
+      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 = null;
+            type = types.nullOr types.path;
+          };
 
-      adminPasswordFile = mkOption {
-        description = ''
-          Default admin password.
-          This option is mutual exclusive with the <literal>adminPassword</literal> option.
-        '';
-        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;
+                };
 
-      secretKey = mkOption {
-        description = "Secret key used for signing.";
-        default = "SW2YcwTIb9zpOOhoPsMm";
-        type = types.str;
-      };
+                groups = mkOption {
+                  description = lib.mdDoc "List of rule groups to import or update.";
+                  default = [];
+                  type = types.listOf (types.submodule {
+                    freeformType = provisioningSettingsFormat.type;
 
-      secretKeyFile = mkOption {
-        description = "Secret key used for signing.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-    };
+                    options.name = mkOption {
+                      description = lib.mdDoc "Name of the rule group. Required.";
+                      type = types.str;
+                    };
 
-    smtp = {
-      enable = mkEnableOption "smtp";
-      host = mkOption {
-        description = "Host to connect to.";
-        default = "localhost:25";
-        type = types.str;
-      };
-      user = mkOption {
-        description = "User used for authentication.";
-        default = "";
-        type = types.str;
-      };
-      password = mkOption {
-        description = ''
-          Password used for authentication.
-          This option is mutual exclusive with the passwordFile option.
-        '';
-        default = "";
-        type = types.str;
-      };
-      passwordFile = mkOption {
-        description = ''
-          Password used for authentication.
-          This option is mutual exclusive with the password option.
-        '';
-        default = null;
-        type = types.nullOr types.path;
-      };
-      fromAddress = mkOption {
-        description = "Email address used for sending.";
-        default = "admin@grafana.localhost";
-        type = types.str;
-      };
-    };
+                    options.folder = mkOption {
+                      description = lib.mdDoc "Name of the folder the rule group will be stored in. Required.";
+                      type = types.str;
+                    };
 
-    users = {
-      allowSignUp = mkOption {
-        description = "Disable user signup / registration.";
-        default = false;
-        type = types.bool;
-      };
+                    options.interval = mkOption {
+                      description = lib.mdDoc "Interval that the rule group should be evaluated at. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
 
-      allowOrgCreate = mkOption {
-        description = "Whether user is allowed to create organizations.";
-        default = false;
-        type = types.bool;
-      };
+                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;
+                    };
 
-      autoAssignOrg = mkOption {
-        description = "Whether to automatically assign new users to default org.";
-        default = true;
-        type = types.bool;
-      };
+                    options.uid = mkOption {
+                      description = lib.mdDoc "Unique identifier for the rule. Required.";
+                      type = types.str;
+                    };
+                  });
+                };
+              };
+            });
+            example = literalExpression ''
+              {
+                apiVersion = 1;
 
-      autoAssignOrgRole = mkOption {
-        description = "Default role new users will be auto assigned.";
-        default = "Viewer";
-        type = types.enum ["Viewer" "Editor"];
-      };
-    };
+                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";
+                  }];
+                }];
 
-    auth = {
-      anonymous = {
-        enable = mkOption {
-          description = "Whether to allow anonymous access.";
-          default = false;
-          type = types.bool;
+                deleteRules = [{
+                  orgId = 1;
+                  uid = "my_id_1";
+                }];
+              }
+            '';
+          };
         };
-        org_name = mkOption {
-          description = "Which organization to allow anonymous access to.";
-          default = "Main Org.";
-          type = types.str;
-        };
-        org_role = mkOption {
-          description = "Which role anonymous users have in the organization.";
-          default = "Viewer";
-          type = types.str;
-        };
-      };
-      google = {
-        enable = mkOption {
-          description = "Whether to allow Google OAuth2.";
-          default = false;
-          type = types.bool;
-        };
-        allowSignUp = mkOption {
-          description = "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 = "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 = "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 = "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;
+          };
 
-    extraOptions = mkOption {
-      description = ''
-        Extra configuration options passed as env variables as specified in
-        <link xlink:href="http://docs.grafana.org/installation/configuration/">documentation</link>,
-        but without GF_ prefix
-      '';
-      default = {};
-      type = with types; attrsOf (either str 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";
+                }];
+              }
+            '';
+          };
+        };
+      };
     };
   };
 
   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";
       }
     ];
 
@@ -645,37 +1255,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.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;
@@ -683,8 +1267,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;
@@ -707,7 +1291,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.optional (cfg.settings.server.protocol == "socket") [ "@chown" ];
         UMask = "0027";
       };
       preStart = ''
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index baa943302a00..65c91b8f79bb 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -24,16 +24,6 @@ let
     + cfg.web.extraConfig
   );
 
-  graphiteApiConfig = pkgs.writeText "graphite-api.yaml" ''
-    search_index: ${dataDir}/index
-    ${optionalString (config.time.timeZone != null) "time_zone: ${config.time.timeZone}"}
-    ${optionalString (cfg.api.finders != []) "finders:"}
-    ${concatMapStringsSep "\n" (f: "  - " + f.moduleName) cfg.api.finders}
-    ${optionalString (cfg.api.functions != []) "functions:"}
-    ${concatMapStringsSep "\n" (f: "  - " + f) cfg.api.functions}
-    ${cfg.api.extraConfig}
-  '';
-
   seyrenConfig = {
     SEYREN_URL = cfg.seyren.seyrenUrl;
     MONGO_URL = cfg.seyren.mongoUrl;
@@ -72,6 +62,8 @@ let
 in {
 
   imports = [
+    (mkRemovedOptionModule ["services" "graphite" "api"] "")
+    (mkRemovedOptionModule ["services" "graphite" "beacon"] "")
     (mkRemovedOptionModule ["services" "graphite" "pager"] "")
   ];
 
@@ -81,125 +73,43 @@ in {
     dataDir = mkOption {
       type = types.path;
       default = "/var/db/graphite";
-      description = ''
+      description = lib.mdDoc ''
         Data directory for graphite.
       '';
     };
 
     web = {
       enable = mkOption {
-        description = "Whether to enable graphite web frontend.";
+        description = lib.mdDoc "Whether to enable graphite web frontend.";
         default = false;
         type = types.bool;
       };
 
       listenAddress = mkOption {
-        description = "Graphite web frontend listen address.";
+        description = lib.mdDoc "Graphite web frontend listen address.";
         default = "127.0.0.1";
         type = types.str;
       };
 
       port = mkOption {
-        description = "Graphite web frontend port.";
+        description = lib.mdDoc "Graphite web frontend port.";
         default = 8080;
-        type = types.int;
+        type = types.port;
       };
 
       extraConfig = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Graphite webapp settings. See:
-          <link xlink:href="http://graphite.readthedocs.io/en/latest/config-local-settings.html"/>
-        '';
-      };
-    };
-
-    api = {
-      enable = mkOption {
-        description = ''
-          Whether to enable graphite api. Graphite api is lightweight alternative
-          to graphite web, with api and without dashboard. It's advised to use
-          grafana as alternative dashboard and influxdb as alternative to
-          graphite carbon.
-
-          For more information visit
-          <link xlink:href="https://graphite-api.readthedocs.org/en/latest/"/>
-        '';
-        default = false;
-        type = types.bool;
-      };
-
-      finders = mkOption {
-        description = "List of finder plugins to load.";
-        default = [];
-        example = literalExpression "[ pkgs.python3Packages.influxgraph ]";
-        type = types.listOf types.package;
-      };
-
-      functions = mkOption {
-        description = "List of functions to load.";
-        default = [
-          "graphite_api.functions.SeriesFunctions"
-          "graphite_api.functions.PieFunctions"
-        ];
-        type = types.listOf types.str;
-      };
-
-      listenAddress = mkOption {
-        description = "Graphite web service listen address.";
-        default = "127.0.0.1";
-        type = types.str;
-      };
-
-      port = mkOption {
-        description = "Graphite api service port.";
-        default = 8080;
-        type = types.int;
-      };
-
-      package = mkOption {
-        description = "Package to use for graphite api.";
-        default = pkgs.python3Packages.graphite_api;
-        defaultText = literalExpression "pkgs.python3Packages.graphite_api";
-        type = types.package;
-      };
-
-      extraConfig = mkOption {
-        description = "Extra configuration for graphite api.";
-        default = ''
-          whisper:
-            directories:
-                - ${dataDir}/whisper
-        '';
-        defaultText = literalExpression ''
-          '''
-            whisper:
-              directories:
-                - ''${config.${opt.dataDir}}/whisper
-          '''
-        '';
-        example = ''
-          allowed_origins:
-            - dashboard.example.com
-          cheat_times: true
-          influxdb:
-            host: localhost
-            port: 8086
-            user: influxdb
-            pass: influxdb
-            db: metrics
-          cache:
-            CACHE_TYPE: 'filesystem'
-            CACHE_DIR: '/tmp/graphite-api-cache'
+          <http://graphite.readthedocs.io/en/latest/config-local-settings.html>
         '';
-        type = types.lines;
       };
     };
 
     carbon = {
       config = mkOption {
-        description = "Content of carbon configuration file.";
+        description = lib.mdDoc "Content of carbon configuration file.";
         default = ''
           [cache]
           # Listen on localhost by default for security reasons
@@ -215,13 +125,13 @@ in {
       };
 
       enableCache = mkOption {
-        description = "Whether to enable carbon cache, the graphite storage daemon.";
+        description = lib.mdDoc "Whether to enable carbon cache, the graphite storage daemon.";
         default = false;
         type = types.bool;
       };
 
       storageAggregation = mkOption {
-        description = "Defines how to aggregate data to lower-precision retentions.";
+        description = lib.mdDoc "Defines how to aggregate data to lower-precision retentions.";
         default = null;
         type = types.nullOr types.str;
         example = ''
@@ -233,7 +143,7 @@ in {
       };
 
       storageSchemas = mkOption {
-        description = "Defines retention rates for storing metrics.";
+        description = lib.mdDoc "Defines retention rates for storing metrics.";
         default = "";
         type = types.nullOr types.str;
         example = ''
@@ -244,21 +154,21 @@ in {
       };
 
       blacklist = mkOption {
-        description = "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 = "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 = ".*";
       };
 
       rewriteRules = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Regular expression patterns that can be used to rewrite metric names
           in a search and replace fashion.
         '';
@@ -272,13 +182,13 @@ in {
       };
 
       enableRelay = mkOption {
-        description = "Whether to enable carbon relay, the carbon replication and sharding service.";
+        description = lib.mdDoc "Whether to enable carbon relay, the carbon replication and sharding service.";
         default = false;
         type = types.bool;
       };
 
       relayRules = mkOption {
-        description = "Relay rules are used to send certain metrics to a certain backend.";
+        description = lib.mdDoc "Relay rules are used to send certain metrics to a certain backend.";
         default = null;
         type = types.nullOr types.str;
         example = ''
@@ -289,13 +199,13 @@ in {
       };
 
       enableAggregator = mkOption {
-        description = "Whether to enable carbon aggregator, the carbon buffering service.";
+        description = lib.mdDoc "Whether to enable carbon aggregator, the carbon buffering service.";
         default = false;
         type = types.bool;
       };
 
       aggregationRules = mkOption {
-        description = "Defines if and how received metrics will be aggregated.";
+        description = lib.mdDoc "Defines if and how received metrics will be aggregated.";
         default = null;
         type = types.nullOr types.str;
         example = ''
@@ -307,43 +217,43 @@ in {
 
     seyren = {
       enable = mkOption {
-        description = "Whether to enable seyren service.";
+        description = lib.mdDoc "Whether to enable seyren service.";
         default = false;
         type = types.bool;
       };
 
       port = mkOption {
-        description = "Seyren listening port.";
+        description = lib.mdDoc "Seyren listening port.";
         default = 8081;
-        type = types.int;
+        type = types.port;
       };
 
       seyrenUrl = mkOption {
         default = "http://localhost:${toString cfg.seyren.port}/";
         defaultText = literalExpression ''"http://localhost:''${toString config.${opt.seyren.port}}/"'';
-        description = "Host where seyren is accessible.";
+        description = lib.mdDoc "Host where seyren is accessible.";
         type = types.str;
       };
 
       graphiteUrl = mkOption {
         default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
         defaultText = literalExpression ''"http://''${config.${opt.web.listenAddress}}:''${toString config.${opt.web.port}}"'';
-        description = "Host where graphite service runs.";
+        description = lib.mdDoc "Host where graphite service runs.";
         type = types.str;
       };
 
       mongoUrl = mkOption {
         default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren";
         defaultText = literalExpression ''"mongodb://''${config.services.mongodb.bind_ip}:27017/seyren"'';
-        description = "Mongodb connection string.";
+        description = lib.mdDoc "Mongodb connection string.";
         type = types.str;
       };
 
       extraConfig = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Extra seyren configuration. See
-          <link xlink:href='https://github.com/scobal/seyren#config' />
+          <https://github.com/scobal/seyren#config>
         '';
         type = types.attrsOf types.str;
         example = literalExpression ''
@@ -354,16 +264,6 @@ in {
         '';
       };
     };
-
-    beacon = {
-      enable = mkEnableOption "graphite beacon";
-
-      config = mkOption {
-        description = "Graphite beacon configuration.";
-        default = {};
-        type = types.attrs;
-      };
-    };
   };
 
   ###### implementation
@@ -489,44 +389,6 @@ in {
       environment.systemPackages = [ pkgs.python3Packages.graphite-web ];
     }))
 
-    (mkIf cfg.api.enable {
-      systemd.services.graphiteApi = {
-        description = "Graphite Api Interface";
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-        environment = {
-          PYTHONPATH = let
-              aenv = pkgs.python3.buildEnv.override {
-                extraLibs = [ cfg.api.package pkgs.cairo pkgs.python3Packages.cffi ] ++ cfg.api.finders;
-              };
-            in "${aenv}/${pkgs.python3.sitePackages}";
-          GRAPHITE_API_CONFIG = graphiteApiConfig;
-          LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
-        };
-        serviceConfig = {
-          ExecStart = ''
-            ${pkgs.python3Packages.waitress}/bin/waitress-serve \
-            --host=${cfg.api.listenAddress} --port=${toString cfg.api.port} \
-            graphite_api.app:app
-          '';
-          User = "graphite";
-          Group = "graphite";
-          PermissionsStartOnly = true;
-        };
-        preStart = ''
-          if ! test -e ${dataDir}/db-created; then
-            mkdir -p ${dataDir}/cache/
-            chmod 0700 ${dataDir}/cache/
-
-            chown graphite:graphite ${cfg.dataDir}
-            chown -R graphite:graphite ${cfg.dataDir}/cache
-
-            touch ${dataDir}/db-created
-          fi
-        '';
-      };
-    })
-
     (mkIf cfg.seyren.enable {
       systemd.services.seyren = {
         description = "Graphite Alerting Dashboard";
@@ -550,25 +412,9 @@ in {
       services.mongodb.enable = mkDefault true;
     })
 
-    (mkIf cfg.beacon.enable {
-      systemd.services.graphite-beacon = {
-        description = "Grpahite Beacon Alerting Daemon";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          ExecStart = ''
-            ${pkgs.python3Packages.graphite_beacon}/bin/graphite-beacon \
-              --config=${pkgs.writeText "graphite-beacon.json" (builtins.toJSON cfg.beacon.config)}
-          '';
-          User = "graphite";
-          Group = "graphite";
-        };
-      };
-    })
-
     (mkIf (
       cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay ||
-      cfg.web.enable || cfg.api.enable ||
-      cfg.seyren.enable || cfg.beacon.enable
+      cfg.web.enable || cfg.seyren.enable
      ) {
       users.users.graphite = {
         uid = config.ids.uids.graphite;
diff --git a/nixos/modules/services/monitoring/hdaps.nix b/nixos/modules/services/monitoring/hdaps.nix
index 2cad3b84d847..59b8b9b3c054 100644
--- a/nixos/modules/services/monitoring/hdaps.nix
+++ b/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/nixos/modules/services/monitoring/heapster.nix b/nixos/modules/services/monitoring/heapster.nix
index 44f53e1890ac..fc63276b62f7 100644
--- a/nixos/modules/services/monitoring/heapster.nix
+++ b/nixos/modules/services/monitoring/heapster.nix
@@ -6,32 +6,28 @@ let
   cfg = config.services.heapster;
 in {
   options.services.heapster = {
-    enable = mkOption {
-      description = "Whether to enable heapster monitoring";
-      default = false;
-      type = types.bool;
-    };
+    enable = mkEnableOption (lib.mdDoc "Heapster monitoring");
 
     source = mkOption {
-      description = "Heapster metric source";
+      description = lib.mdDoc "Heapster metric source";
       example = "kubernetes:https://kubernetes.default";
       type = types.str;
     };
 
     sink = mkOption {
-      description = "Heapster metic sink";
+      description = lib.mdDoc "Heapster metic sink";
       example = "influxdb:http://localhost:8086";
       type = types.str;
     };
 
     extraOpts = mkOption {
-      description = "Heapster extra options";
+      description = lib.mdDoc "Heapster extra options";
       default = "";
       type = types.separatedString " ";
     };
 
     package = mkOption {
-      description = "Package to use by heapster";
+      description = lib.mdDoc "Package to use by heapster";
       default = pkgs.heapster;
       defaultText = literalExpression "pkgs.heapster";
       type = types.package;
diff --git a/nixos/modules/services/monitoring/incron.nix b/nixos/modules/services/monitoring/incron.nix
index 2681c35d6a01..3766f1fa238d 100644
--- a/nixos/modules/services/monitoring/incron.nix
+++ b/nixos/modules/services/monitoring/incron.nix
@@ -17,22 +17,22 @@ 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.
         '';
       };
 
       allow = mkOption {
         type = types.nullOr (types.listOf types.str);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Users allowed to use incrontab.
 
           If empty then no user will be allowed to have their own incrontab.
-          If <literal>null</literal> then will defer to <option>deny</option>.
-          If both <option>allow</option> and <option>deny</option> are null
+          If `null` then will defer to {option}`deny`.
+          If both {option}`allow` and {option}`deny` are null
           then all users will be allowed to have their own incrontab.
         '';
       };
@@ -40,13 +40,13 @@ in
       deny = mkOption {
         type = types.nullOr (types.listOf types.str);
         default = null;
-        description = "Users forbidden from using incrontab.";
+        description = lib.mdDoc "Users forbidden from using incrontab.";
       };
 
       systab = mkOption {
         type = types.lines;
         default = "";
-        description = "The system incrontab contents.";
+        description = lib.mdDoc "The system incrontab contents.";
         example = ''
           /var/mail IN_CLOSE_WRITE abc $@/$#
           /tmp IN_ALL_EVENTS efg $@/$# $&
@@ -57,7 +57,7 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.rsync ]";
-        description = "Extra packages available to the system incrontab.";
+        description = lib.mdDoc "Extra packages available to the system incrontab.";
       };
 
     };
diff --git a/nixos/modules/services/monitoring/kapacitor.nix b/nixos/modules/services/monitoring/kapacitor.nix
index a79c647becfc..727b694047b4 100644
--- a/nixos/modules/services/monitoring/kapacitor.nix
+++ b/nixos/modules/services/monitoring/kapacitor.nix
@@ -57,29 +57,29 @@ let
 in
 {
   options.services.kapacitor = {
-    enable = mkEnableOption "kapacitor";
+    enable = mkEnableOption (lib.mdDoc "kapacitor");
 
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/kapacitor";
-      description = "Location where Kapacitor stores its state";
+      description = lib.mdDoc "Location where Kapacitor stores its state";
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 9092;
-      description = "Port of Kapacitor";
+      description = lib.mdDoc "Port of Kapacitor";
     };
 
     bind = mkOption {
       type = types.str;
       default = "";
       example = "0.0.0.0";
-      description = "Address to bind to. The default is to bind to all addresses";
+      description = lib.mdDoc "Address to bind to. The default is to bind to all addresses";
     };
 
     extraConfig = mkOption {
-      description = "These lines go into kapacitord.conf verbatim.";
+      description = lib.mdDoc "These lines go into kapacitord.conf verbatim.";
       default = "";
       type = types.lines;
     };
@@ -87,70 +87,70 @@ in
     user = mkOption {
       type = types.str;
       default = "kapacitor";
-      description = "User account under which Kapacitor runs";
+      description = lib.mdDoc "User account under which Kapacitor runs";
     };
 
     group = mkOption {
       type = types.str;
       default = "kapacitor";
-      description = "Group under which Kapacitor runs";
+      description = lib.mdDoc "Group under which Kapacitor runs";
     };
 
     taskSnapshotInterval = mkOption {
       type = types.str;
-      description = "Specifies how often to snapshot the task state  (in InfluxDB time units)";
+      description = lib.mdDoc "Specifies how often to snapshot the task state  (in InfluxDB time units)";
       default = "1m0s";
     };
 
     loadDirectory = mkOption {
       type = types.nullOr types.path;
-      description = "Directory where to load services from, such as tasks, templates and handlers (or null to disable service loading on startup)";
+      description = lib.mdDoc "Directory where to load services from, such as tasks, templates and handlers (or null to disable service loading on startup)";
       default = null;
     };
 
     defaultDatabase = {
-      enable = mkEnableOption "kapacitor.defaultDatabase";
+      enable = mkEnableOption (lib.mdDoc "kapacitor.defaultDatabase");
 
       url = mkOption {
-        description = "The URL to an InfluxDB server that serves as the default database";
+        description = lib.mdDoc "The URL to an InfluxDB server that serves as the default database";
         example = "http://localhost:8086";
         type = types.str;
       };
 
       username = mkOption {
-        description = "The username to connect to the remote InfluxDB server";
+        description = lib.mdDoc "The username to connect to the remote InfluxDB server";
         type = types.str;
       };
 
       password = mkOption {
-        description = "The password to connect to the remote InfluxDB server";
+        description = lib.mdDoc "The password to connect to the remote InfluxDB server";
         type = types.str;
       };
     };
 
     alerta = {
-      enable = mkEnableOption "kapacitor alerta integration";
+      enable = mkEnableOption (lib.mdDoc "kapacitor alerta integration");
 
       url = mkOption {
-        description = "The URL to the Alerta REST API";
+        description = lib.mdDoc "The URL to the Alerta REST API";
         default = "http://localhost:5000";
         type = types.str;
       };
 
       token = mkOption {
-        description = "Default Alerta authentication token";
+        description = lib.mdDoc "Default Alerta authentication token";
         type = types.str;
         default = "";
       };
 
       environment = mkOption {
-        description = "Default Alerta environment";
+        description = lib.mdDoc "Default Alerta environment";
         type = types.str;
         default = "Production";
       };
 
       origin = mkOption {
-        description = "Default origin of alert";
+        description = lib.mdDoc "Default origin of alert";
         type = types.str;
         default = "kapacitor";
       };
diff --git a/nixos/modules/services/monitoring/karma.nix b/nixos/modules/services/monitoring/karma.nix
new file mode 100644
index 000000000000..85dbc81f443f
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/kthxbye.nix b/nixos/modules/services/monitoring/kthxbye.nix
new file mode 100644
index 000000000000..3f988dcb722f
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/loki.nix b/nixos/modules/services/monitoring/loki.nix
index ebac70c30c22..11bb8497c9bb 100644
--- a/nixos/modules/services/monitoring/loki.nix
+++ b/nixos/modules/services/monitoring/loki.nix
@@ -12,12 +12,12 @@ let
 
 in {
   options.services.loki = {
-    enable = mkEnableOption "loki";
+    enable = mkEnableOption (lib.mdDoc "loki");
 
     user = mkOption {
       type = types.str;
       default = "loki";
-      description = ''
+      description = lib.mdDoc ''
         User under which the Loki service runs.
       '';
     };
@@ -25,7 +25,7 @@ in {
     group = mkOption {
       type = types.str;
       default = "loki";
-      description = ''
+      description = lib.mdDoc ''
         Group under which the Loki service runs.
       '';
     };
@@ -33,7 +33,7 @@ in {
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/loki";
-      description = ''
+      description = lib.mdDoc ''
         Specify the directory for Loki.
       '';
     };
@@ -41,7 +41,7 @@ in {
     configuration = mkOption {
       type = (pkgs.formats.json {}).type;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Specify the configuration for Loki in Nix.
       '';
     };
@@ -49,7 +49,7 @@ in {
     configFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Specify a configuration file that Loki should use.
       '';
     };
@@ -58,7 +58,7 @@ in {
       type = types.listOf types.str;
       default = [];
       example = [ "--server.http-listen-port=3101" ];
-      description = ''
+      description = lib.mdDoc ''
         Specify a list of additional command line flags,
         which get escaped and are then passed to Loki.
       '';
diff --git a/nixos/modules/services/monitoring/longview.nix b/nixos/modules/services/monitoring/longview.nix
index 9c38956f9ba8..5825cab0134c 100644
--- a/nixos/modules/services/monitoring/longview.nix
+++ b/nixos/modules/services/monitoring/longview.nix
@@ -16,7 +16,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, system metrics will be sent to Linode LongView.
         '';
       };
@@ -25,12 +25,12 @@ in {
         type = types.str;
         default = "";
         example = "01234567-89AB-CDEF-0123456789ABCDEF";
-        description = ''
+        description = lib.mdDoc ''
           Longview API key. To get this, look in Longview settings which
           are found at https://manager.linode.com/longview/.
 
           Warning: this secret is stored in the world-readable Nix store!
-          Use <option>apiKeyFile</option> instead.
+          Use {option}`apiKeyFile` instead.
         '';
       };
 
@@ -38,12 +38,12 @@ in {
         type = types.nullOr types.path;
         default = null;
         example = "/run/keys/longview-api-key";
-        description = ''
+        description = lib.mdDoc ''
           A file containing the Longview API key.
           To get this, look in Longview settings which
           are found at https://manager.linode.com/longview/.
 
-          <option>apiKeyFile</option> takes precedence over <option>apiKey</option>.
+          {option}`apiKeyFile` takes precedence over {option}`apiKey`.
         '';
       };
 
@@ -51,7 +51,7 @@ in {
         type = types.str;
         default = "";
         example = "http://127.0.0.1/server-status";
-        description = ''
+        description = lib.mdDoc ''
           The Apache status page URL. If provided, Longview will
           gather statistics from this location. This requires Apache
           mod_status to be loaded and enabled.
@@ -62,7 +62,7 @@ in {
         type = types.str;
         default = "";
         example = "http://127.0.0.1/nginx_status";
-        description = ''
+        description = lib.mdDoc ''
           The Nginx status page URL. Longview will gather statistics
           from this URL. This requires the Nginx stub_status module to
           be enabled and configured at the given location.
@@ -72,7 +72,7 @@ in {
       mysqlUser = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           The user for connecting to the MySQL database. If provided,
           Longview will connect to MySQL and collect statistics about
           queries, etc. This user does not need to have been granted
@@ -83,10 +83,10 @@ in {
       mysqlPassword = mkOption {
         type = types.str;
         default = "";
-        description = ''
-          The password corresponding to <option>mysqlUser</option>.
+        description = lib.mdDoc ''
+          The password corresponding to {option}`mysqlUser`.
           Warning: this is stored in cleartext in the Nix store!
-          Use <option>mysqlPasswordFile</option> instead.
+          Use {option}`mysqlPasswordFile` instead.
         '';
       };
 
@@ -94,8 +94,8 @@ in {
         type = types.nullOr types.path;
         default = null;
         example = "/run/keys/dbpassword";
-        description = ''
-          A file containing the password corresponding to <option>mysqlUser</option>.
+        description = lib.mdDoc ''
+          A file containing the password corresponding to {option}`mysqlUser`.
         '';
       };
 
diff --git a/nixos/modules/services/monitoring/mackerel-agent.nix b/nixos/modules/services/monitoring/mackerel-agent.nix
index aeb6247abd8b..4185cd76c4eb 100644
--- a/nixos/modules/services/monitoring/mackerel-agent.nix
+++ b/nixos/modules/services/monitoring/mackerel-agent.nix
@@ -7,33 +7,33 @@ 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"`
       '';
     };
 
     settings = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Options for mackerel-agent.conf.
 
         Documentation:
-        <link xlink:href="https://mackerel.io/docs/entry/spec/agent"/>
+        <https://mackerel.io/docs/entry/spec/agent>
       '';
 
       default = {};
@@ -48,18 +48,18 @@ in {
         options.host_status = {
           on_start = mkOption {
             type = types.enum [ "working" "standby" "maintenance" "poweroff" ];
-            description = "Host status after agent startup.";
+            description = lib.mdDoc "Host status after agent startup.";
             default = "working";
           };
           on_stop = mkOption {
             type = types.enum [ "working" "standby" "maintenance" "poweroff" ];
-            description = "Host status after agent shutdown.";
+            description = lib.mdDoc "Host status after agent shutdown.";
             default = "poweroff";
           };
         };
 
         options.diagnostic =
-          mkEnableOption "Collect memory usage for the agent itself";
+          mkEnableOption (lib.mdDoc "Collect memory usage for the agent itself");
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/metricbeat.nix b/nixos/modules/services/monitoring/metricbeat.nix
index e75039daa107..310c9d8ed509 100644
--- a/nixos/modules/services/monitoring/metricbeat.nix
+++ b/nixos/modules/services/monitoring/metricbeat.nix
@@ -19,30 +19,30 @@ in
 
     services.metricbeat = {
 
-      enable = mkEnableOption "metricbeat";
+      enable = mkEnableOption (lib.mdDoc "metricbeat");
 
       package = mkOption {
         type = types.package;
         default = pkgs.metricbeat;
         defaultText = literalExpression "pkgs.metricbeat";
         example = literalExpression "pkgs.metricbeat7";
-        description = ''
+        description = lib.mdDoc ''
           The metricbeat package to use
         '';
       };
 
       modules = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Metricbeat modules are responsible for reading metrics from the various sources.
 
-          This is like <literal>services.metricbeat.settings.metricbeat.modules</literal>,
+          This is like `services.metricbeat.settings.metricbeat.modules`,
           but structured as an attribute set. This has the benefit that multiple
           NixOS modules can contribute settings to a single metricbeat module.
 
-          A module can be specified multiple times by choosing a different <literal>&lt;name></literal>
-          for each, but setting <xref linkend="opt-services.metricbeat.modules._name_.module"/> to the same value.
+          A module can be specified multiple times by choosing a different `<name>`
+          for each, but setting [](#opt-services.metricbeat.modules._name_.module) to the same value.
 
-          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>.
         '';
         default = {};
         type = types.attrsOf (types.submodule ({ name, ... }: {
@@ -51,11 +51,11 @@ in
             module = mkOption {
               type = types.str;
               default = name;
-              description = ''
+              description = lib.mdDoc ''
                 The name of the module.
 
-                Look for the value after <literal>module:</literal> on the individual
-                module pages linked from <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-modules.html"/>.
+                Look for the value after `module:` on the individual
+                module pages linked from <https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-modules.html>.
               '';
             };
           };
@@ -80,18 +80,18 @@ in
             name = mkOption {
               type = types.str;
               default = "";
-              description = ''
+              description = lib.mdDoc ''
                 Name of the beat. Defaults to the hostname.
-                See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-general-options.html#_name"/>.
+                See <https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-general-options.html#_name>.
               '';
             };
 
             tags = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = ''
+              description = lib.mdDoc ''
                 Tags to place on the shipped metrics.
-                See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-general-options.html#_tags_2"/>.
+                See <https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-general-options.html#_tags_2>.
               '';
             };
 
@@ -99,17 +99,17 @@ 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>.
               '';
             };
           };
         };
         default = {};
-        description = ''
-          Configuration for metricbeat. See <link xlink:href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuring-howto-metricbeat.html"/> for supported values.
+        description = lib.mdDoc ''
+          Configuration for metricbeat. See <https://www.elastic.co/guide/en/beats/metricbeat/current/configuring-howto-metricbeat.html> for supported values.
         '';
       };
 
diff --git a/nixos/modules/services/monitoring/mimir.nix b/nixos/modules/services/monitoring/mimir.nix
new file mode 100644
index 000000000000..568066990f23
--- /dev/null
+++ b/nixos/modules/services/monitoring/mimir.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) escapeShellArgs mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.mimir;
+
+  settingsFormat = pkgs.formats.yaml {};
+in {
+  options.services.mimir = {
+    enable = mkEnableOption (lib.mdDoc "mimir");
+
+    configuration = mkOption {
+      type = (pkgs.formats.json {}).type;
+      default = {};
+      description = lib.mdDoc ''
+        Specify the configuration for Mimir in Nix.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Specify a configuration file that Mimir should use.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # for mimirtool
+    environment.systemPackages = [ pkgs.mimir ];
+
+    assertions = [{
+      assertion = (
+        (cfg.configuration == {} -> cfg.configFile != null) &&
+        (cfg.configFile != null -> cfg.configuration == {})
+      );
+      message  = ''
+        Please specify either
+        'services.mimir.configuration' or
+        'services.mimir.configFile'.
+      '';
+    }];
+
+    systemd.services.mimir = {
+      description = "mimir Service Daemon";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = let
+        conf = if cfg.configFile == null
+               then settingsFormat.generate "config.yaml" cfg.configuration
+               else cfg.configFile;
+      in
+      {
+        ExecStart = "${pkgs.mimir}/bin/mimir --config.file=${conf}";
+        DynamicUser = true;
+        Restart = "always";
+        ProtectSystem = "full";
+        DevicePolicy = "closed";
+        NoNewPrivileges = true;
+        WorkingDirectory = "/var/lib/mimir";
+        StateDirectory = "mimir";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/monit.nix b/nixos/modules/services/monitoring/monit.nix
index 379ee967620e..a22bbc9046ba 100644
--- a/nixos/modules/services/monitoring/monit.nix
+++ b/nixos/modules/services/monitoring/monit.nix
@@ -9,12 +9,12 @@ in
 {
   options.services.monit = {
 
-    enable = mkEnableOption "Monit";
+    enable = mkEnableOption (lib.mdDoc "Monit");
 
     config = mkOption {
       type = types.lines;
       default = "";
-      description = "monitrc content";
+      description = lib.mdDoc "monitrc content";
     };
 
   };
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index 4fddb1e37e2e..9461bd3f35b8 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -138,29 +138,29 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable Munin Node agent. Munin node listens on 0.0.0.0 and
           by default accepts connections only from 127.0.0.1 for security reasons.
 
-          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/architecture/index.html' />.
+          See <http://guide.munin-monitoring.org/en/latest/architecture/index.html>.
         '';
       };
 
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
-          <filename>munin-node.conf</filename> extra configuration. See
-          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html' />
+        description = lib.mdDoc ''
+          {file}`munin-node.conf` extra configuration. See
+          <http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
         '';
       };
 
       extraPluginConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
-          <filename>plugin-conf.d</filename> extra plugin configuration. See
-          <link xlink:href='http://guide.munin-monitoring.org/en/latest/plugin/use.html' />
+        description = lib.mdDoc ''
+          {file}`plugin-conf.d` extra plugin configuration. See
+          <http://guide.munin-monitoring.org/en/latest/plugin/use.html>
         '';
         example = ''
           [fail2ban_*]
@@ -171,7 +171,7 @@ in
       extraPlugins = mkOption {
         default = {};
         type = with types; attrsOf path;
-        description = ''
+        description = lib.mdDoc ''
           Additional Munin plugins to activate. Keys are the name of the plugin
           symlink, values are the path to the underlying plugin script. You
           can use the same plugin script multiple times (e.g. for wildcard
@@ -179,15 +179,15 @@ in
 
           Note that these plugins do not participate in autoconfiguration. If
           you want to autoconfigure additional plugins, use
-          <option>services.munin-node.extraAutoPlugins</option>.
+          {option}`services.munin-node.extraAutoPlugins`.
 
           Plugins enabled in this manner take precedence over autoconfigured
           plugins.
 
           Plugins will be copied into the Nix store, and it will attempt to
           modify them to run properly by fixing hardcoded references to
-          <literal>/bin</literal>, <literal>/usr/bin</literal>,
-          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+          `/bin`, `/usr/bin`,
+          `/sbin`, and `/usr/sbin`.
         '';
         example = literalExpression ''
           {
@@ -201,24 +201,24 @@ in
       extraAutoPlugins = mkOption {
         default = [];
         type = with types; listOf path;
-        description = ''
+        description = lib.mdDoc ''
           Additional Munin plugins to autoconfigure, using
-          <literal>munin-node-configure --suggest</literal>. These should be
+          `munin-node-configure --suggest`. These should be
           the actual paths to the plugin files (or directories containing them),
           not just their names.
 
           If you want to manually enable individual plugins instead, use
-          <option>services.munin-node.extraPlugins</option>.
+          {option}`services.munin-node.extraPlugins`.
 
           Note that only plugins that have the 'autoconfig' capability will do
           anything if listed here, since plugins that cannot autoconfigure
           won't be automatically enabled by
-          <literal>munin-node-configure</literal>.
+          `munin-node-configure`.
 
           Plugins will be copied into the Nix store, and it will attempt to
           modify them to run properly by fixing hardcoded references to
-          <literal>/bin</literal>, <literal>/usr/bin</literal>,
-          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+          `/bin`, `/usr/bin`,
+          `/sbin`, and `/usr/sbin`.
         '';
         example = literalExpression ''
           [
@@ -234,14 +234,14 @@ in
         # NaNs in the output.
         default = [ "munin_stats" ];
         type = with types; listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Munin plugins to disable, even if
-          <literal>munin-node-configure --suggest</literal> tries to enable
+          `munin-node-configure --suggest` tries to enable
           them. To disable a wildcard plugin, use an actual wildcard, as in
           the example.
 
           munin_stats is disabled by default as it tries to read
-          <literal>/var/log/munin/munin-update.log</literal> for timing
+          `/var/log/munin/munin-update.log` for timing
           information, and the NixOS build of Munin does not write this file.
         '';
         example = [ "diskstats" "zfs_usage_*" ];
@@ -253,12 +253,12 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable munin-cron. Takes care of all heavy lifting to collect data from
           nodes and draws graphs to html. Runs munin-update, munin-limits,
           munin-graphs and munin-html in that order.
 
-          HTML output is in <filename>/var/www/munin/</filename>, configure your
+          HTML output is in {file}`/var/www/munin/`, configure your
           favourite webserver to serve static files.
         '';
       };
@@ -266,11 +266,11 @@ in
       extraGlobalConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
-          <filename>munin.conf</filename> extra global configuration.
-          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />.
+        description = lib.mdDoc ''
+          {file}`munin.conf` extra global configuration.
+          See <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
           Useful to setup notifications, see
-          <link xlink:href='http://guide.munin-monitoring.org/en/latest/tutorial/alert.html' />
+          <http://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
         '';
         example = ''
           contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
@@ -280,10 +280,10 @@ in
       hosts = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Definitions of hosts of nodes to collect data from. Needs at least one
           host for cron to succeed. See
-          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />
+          <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
         '';
         example = literalExpression ''
           '''
@@ -296,7 +296,7 @@ in
       extraCSS = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Custom styling for the HTML that munin-cron generates. This will be
           appended to the CSS files used by munin-cron and will thus take
           precedence over the builtin styles.
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 69173ce4e44e..8feff22c1182 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/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 {
@@ -125,33 +125,33 @@ in
           debug_file = "/var/log/nagios/debug.log";
         };
         default = {};
-        description = "Configuration to add to /etc/nagios.cfg";
+        description = lib.mdDoc "Configuration to add to /etc/nagios.cfg";
       };
 
       validateConfig = mkOption {
         type = types.bool;
         default = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform;
         defaultText = literalExpression "pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform";
-        description = "if true, the syntax of the nagios configuration file is checked at build time";
+        description = lib.mdDoc "if true, the syntax of the nagios configuration file is checked at build time";
       };
 
       cgiConfigFile = mkOption {
         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 {
@@ -164,9 +164,9 @@ in
             sslServerKey = "/var/lib/acme/example.org/key.pem";
           }
         '';
-        description = ''
-          Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
-          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+        description = lib.mdDoc ''
+          Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
+          See [](#opt-services.httpd.virtualHosts) for further information.
         '';
       };
     };
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index f528d1830424..92c870bb23f1 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -49,30 +49,30 @@ let
 in {
   options = {
     services.netdata = {
-      enable = mkEnableOption "netdata";
+      enable = mkEnableOption (lib.mdDoc "netdata");
 
       package = mkOption {
         type = types.package;
         default = pkgs.netdata;
         defaultText = literalExpression "pkgs.netdata";
-        description = "Netdata package to use.";
+        description = lib.mdDoc "Netdata package to use.";
       };
 
       user = mkOption {
         type = types.str;
         default = "netdata";
-        description = "User account under which netdata runs.";
+        description = lib.mdDoc "User account under which netdata runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "netdata";
-        description = "Group under which netdata runs.";
+        description = lib.mdDoc "Group under which netdata runs.";
       };
 
       configText = mkOption {
         type = types.nullOr types.lines;
-        description = "Verbatim netdata.conf, cannot be combined with config.";
+        description = lib.mdDoc "Verbatim netdata.conf, cannot be combined with config.";
         default = null;
         example = ''
           [global]
@@ -86,7 +86,7 @@ in {
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable python-based plugins
           '';
         };
@@ -101,7 +101,7 @@ in {
               ps.dnspython
             ]
           '';
-          description = ''
+          description = lib.mdDoc ''
             Extra python packages available at runtime
             to enable additional python plugins.
           '';
@@ -114,14 +114,14 @@ in {
         example = literalExpression ''
           [ "/path/to/plugins.d" ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra paths to add to the netdata global "plugins directory"
           option.  Useful for when you want to include your own
           collection scripts.
-          </para><para>
+
           Details about writing a custom netdata plugin are available at:
-          <link xlink:href="https://docs.netdata.cloud/collectors/plugins.d/"/>
-          </para><para>
+          <https://docs.netdata.cloud/collectors/plugins.d/>
+
           Cannot be combined with configText.
         '';
       };
@@ -129,7 +129,7 @@ in {
       config = mkOption {
         type = types.attrsOf types.attrs;
         default = {};
-        description = "netdata.conf configuration as nix attributes. cannot be combined with configText.";
+        description = lib.mdDoc "netdata.conf configuration as nix attributes. cannot be combined with configText.";
         example = literalExpression ''
           global = {
             "debug log" = "syslog";
@@ -142,7 +142,7 @@ in {
       configDir = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Complete netdata config directory except netdata.conf.
           The default configuration is merged with changes
           defined in this option.
@@ -162,11 +162,11 @@ in {
       enableAnalyticsReporting = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable reporting of anonymous usage statistics to Netdata Inc. via either
           Google Analytics (in versions prior to 1.29.4), or Netdata Inc.'s
           self-hosted PostHog (in versions 1.29.4 and later).
-          See: <link xlink:href="https://learn.netdata.cloud/docs/agent/anonymous-statistics"/>
+          See: <https://learn.netdata.cloud/docs/agent/anonymous-statistics>
         '';
       };
     };
@@ -186,7 +186,7 @@ in {
       description = "Real time performance monitoring";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = (with pkgs; [ curl gawk iproute2 which procps ])
+      path = (with pkgs; [ curl gawk iproute2 which procps bash ])
         ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages)
         ++ lib.optional config.virtualisation.libvirtd.enable (config.virtualisation.libvirtd.package);
       environment = {
@@ -201,6 +201,10 @@ in {
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c /etc/netdata/netdata.conf";
         ExecReload = "${pkgs.util-linux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
+        ExecStartPost = pkgs.writeShellScript "wait-for-netdata-up" ''
+          while [ "$(${pkgs.netdata}/bin/netdatacli ping)" != pong ]; do sleep 0.5; done
+        '';
+
         TimeoutStopSec = 60;
         Restart = "on-failure";
         # User and group
diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix
index ec71365ba3c1..3540d91fc9f3 100644
--- a/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixos/modules/services/monitoring/parsedmarc.nix
@@ -3,32 +3,44 @@
 let
   cfg = config.services.parsedmarc;
   opt = options.services.parsedmarc;
-  ini = pkgs.formats.ini {};
+  isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+  ini = pkgs.formats.ini {
+    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 hashString "sha256" v._secret
+        else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+    };
+  };
+  inherit (builtins) elem isAttrs isString isInt isList typeOf hashString;
 in
 {
   options.services.parsedmarc = {
 
-    enable = lib.mkEnableOption ''
+    enable = lib.mkEnableOption (lib.mdDoc ''
       parsedmarc, a DMARC report monitoring service
-    '';
+    '');
 
     provision = {
       localMail = {
         enable = lib.mkOption {
           type = lib.types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether Postfix and Dovecot should be set up to receive
             mail locally. parsedmarc will be configured to watch the
             local inbox as the automatically created user specified in
-            <xref linkend="opt-services.parsedmarc.provision.localMail.recipientName" />
+            [](#opt-services.parsedmarc.provision.localMail.recipientName)
           '';
         };
 
         recipientName = lib.mkOption {
           type = lib.types.str;
           default = "dmarc";
-          description = ''
+          description = lib.mdDoc ''
             The DMARC mail recipient name, i.e. the name part of the
             email address which receives DMARC reports.
 
@@ -42,7 +54,7 @@ in
           default = config.networking.fqdn;
           defaultText = lib.literalExpression "config.networking.fqdn";
           example = "monitoring.example.com";
-          description = ''
+          description = lib.mdDoc ''
             The hostname to use when configuring Postfix.
 
             Should correspond to the host's fully qualified domain
@@ -56,15 +68,13 @@ in
       geoIp = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = ''
-          Whether to enable and configure the <link
-          linkend="opt-services.geoipupdate.enable">geoipupdate</link>
+        description = lib.mdDoc ''
+          Whether to enable and configure the [geoipupdate](#opt-services.geoipupdate.enable)
           service to automatically fetch GeoIP databases. Not crucial,
           but recommended for full functionality.
 
-          To finish the setup, you need to manually set the <xref
-          linkend="opt-services.geoipupdate.settings.AccountID" /> and
-          <xref linkend="opt-services.geoipupdate.settings.LicenseKey" />
+          To finish the setup, you need to manually set the [](#opt-services.geoipupdate.settings.AccountID) and
+          [](#opt-services.geoipupdate.settings.LicenseKey)
           options.
         '';
       };
@@ -72,7 +82,7 @@ in
       elasticsearch = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to set up and use a local instance of Elasticsearch.
         '';
       };
@@ -85,11 +95,11 @@ in
             config.${opt.provision.elasticsearch} && config.${options.services.grafana.enable}
           '';
           apply = x: x && cfg.provision.elasticsearch;
-          description = ''
+          description = lib.mdDoc ''
             Whether the automatically provisioned Elasticsearch
             instance should be added as a grafana datasource. Has no
             effect unless
-            <xref linkend="opt-services.parsedmarc.provision.elasticsearch" />
+            [](#opt-services.parsedmarc.provision.elasticsearch)
             is also enabled.
           '';
         };
@@ -98,7 +108,7 @@ in
           type = lib.types.bool;
           default = config.services.grafana.enable;
           defaultText = lib.literalExpression "config.services.grafana.enable";
-          description = ''
+          description = lib.mdDoc ''
             Whether the official parsedmarc grafana dashboard should
             be provisioned to the local grafana instance.
           '';
@@ -107,11 +117,35 @@ in
     };
 
     settings = lib.mkOption {
-      description = ''
+      example = lib.literalExpression ''
+        {
+          imap = {
+            host = "imap.example.com";
+            user = "alice@example.com";
+            password = { _secret = "/run/keys/imap_password" };
+            watch = true;
+          };
+          splunk_hec = {
+            url = "https://splunkhec.example.com";
+            token = { _secret = "/run/keys/splunk_token" };
+            index = "email";
+          };
+        }
+      '';
+      description = lib.mdDoc ''
         Configuration parameters to set in
-        <filename>parsedmarc.ini</filename>. For a full list of
+        {file}`parsedmarc.ini`. For a full list of
         available parameters, see
-        <link xlink:href="https://domainaware.github.io/parsedmarc/#configuration-file" />.
+        <https://domainaware.github.io/parsedmarc/#configuration-file>.
+
+        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 to get a better picture of
+        this: in the resulting {file}`parsedmarc.ini`
+        file, the `splunk_hec.token` key will be set
+        to the contents of the
+        {file}`/run/keys/splunk_token` file.
       '';
 
       type = lib.types.submodule {
@@ -122,7 +156,7 @@ in
             save_aggregate = lib.mkOption {
               type = lib.types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Save aggregate report data to Elasticsearch and/or Splunk.
               '';
             };
@@ -130,7 +164,7 @@ in
             save_forensic = lib.mkOption {
               type = lib.types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Save forensic report data to Elasticsearch and/or Splunk.
               '';
             };
@@ -140,7 +174,7 @@ in
             host = lib.mkOption {
               type = lib.types.str;
               default = "localhost";
-              description = ''
+              description = lib.mdDoc ''
                 The IMAP server hostname or IP address.
               '';
             };
@@ -148,7 +182,7 @@ in
             port = lib.mkOption {
               type = lib.types.port;
               default = 993;
-              description = ''
+              description = lib.mdDoc ''
                 The IMAP server port.
               '';
             };
@@ -156,7 +190,7 @@ in
             ssl = lib.mkOption {
               type = lib.types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Use an encrypted SSL/TLS connection.
               '';
             };
@@ -164,23 +198,29 @@ in
             user = lib.mkOption {
               type = with lib.types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The IMAP server username.
               '';
             };
 
             password = lib.mkOption {
-              type = with lib.types; nullOr path;
+              type = with lib.types; nullOr (either path (attrsOf path));
               default = null;
-              description = ''
-                The path to a file containing the IMAP server password.
+              description = lib.mdDoc ''
+                The IMAP server password.
+
+                Always handled as a secret whether the value is
+                wrapped in a `{ _secret = ...; }`
+                attrset or not (refer to [](#opt-services.parsedmarc.settings) for
+                details).
               '';
+              apply = x: if isAttrs x || x == null then x else { _secret = x; };
             };
 
             watch = lib.mkOption {
               type = lib.types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Use the IMAP IDLE command to process messages as they arrive.
               '';
             };
@@ -188,7 +228,7 @@ in
             delete = lib.mkOption {
               type = lib.types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Delete messages after processing them, instead of archiving them.
               '';
             };
@@ -198,7 +238,7 @@ in
             host = lib.mkOption {
               type = with lib.types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The SMTP server hostname or IP address.
               '';
             };
@@ -206,7 +246,7 @@ in
             port = lib.mkOption {
               type = with lib.types; nullOr port;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The SMTP server port.
               '';
             };
@@ -214,7 +254,7 @@ in
             ssl = lib.mkOption {
               type = with lib.types; nullOr bool;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Use an encrypted SSL/TLS connection.
               '';
             };
@@ -222,24 +262,30 @@ in
             user = lib.mkOption {
               type = with lib.types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The SMTP server username.
               '';
             };
 
             password = lib.mkOption {
-              type = with lib.types; nullOr path;
+              type = with lib.types; nullOr (either path (attrsOf path));
               default = null;
-              description = ''
-                The path to a file containing the SMTP server password.
+              description = lib.mdDoc ''
+                The SMTP server password.
+
+                Always handled as a secret whether the value is
+                wrapped in a `{ _secret = ...; }`
+                attrset or not (refer to [](#opt-services.parsedmarc.settings) for
+                details).
               '';
+              apply = x: if isAttrs x || x == null then x else { _secret = x; };
             };
 
             from = lib.mkOption {
               type = with lib.types; nullOr str;
               default = null;
-              description = ''
-                The <literal>From</literal> address to use for the
+              description = lib.mdDoc ''
+                The `From` address to use for the
                 outgoing mail.
               '';
             };
@@ -247,7 +293,7 @@ in
             to = lib.mkOption {
               type = with lib.types; nullOr (listOf str);
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 The addresses to send outgoing mail to.
               '';
             };
@@ -258,7 +304,7 @@ in
               default = [];
               type = with lib.types; listOf str;
               apply = x: if x == [] then null else lib.concatStringsSep "," x;
-              description = ''
+              description = lib.mdDoc ''
                 A list of Elasticsearch hosts to push parsed reports
                 to.
               '';
@@ -267,25 +313,31 @@ in
             user = lib.mkOption {
               type = with lib.types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Username to use when connecting to Elasticsearch, if
                 required.
               '';
             };
 
             password = lib.mkOption {
-              type = with lib.types; nullOr path;
+              type = with lib.types; nullOr (either path (attrsOf path));
               default = null;
-              description = ''
-                The path to a file containing the password to use when
-                connecting to Elasticsearch, if required.
+              description = lib.mdDoc ''
+                The password to use when connecting to Elasticsearch,
+                if required.
+
+                Always handled as a secret whether the value is
+                wrapped in a `{ _secret = ...; }`
+                attrset or not (refer to [](#opt-services.parsedmarc.settings) for
+                details).
               '';
+              apply = x: if isAttrs x || x == null then x else { _secret = x; };
             };
 
             ssl = lib.mkOption {
               type = lib.types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to use an encrypted SSL/TLS connection.
               '';
             };
@@ -293,69 +345,12 @@ in
             cert_path = lib.mkOption {
               type = lib.types.path;
               default = "/etc/ssl/certs/ca-certificates.crt";
-              description = ''
+              description = lib.mdDoc ''
                 The path to a TLS certificate bundle used to verify
                 the server's certificate.
               '';
             };
           };
-
-          kafka = {
-            hosts = lib.mkOption {
-              default = [];
-              type = with lib.types; listOf str;
-              apply = x: if x == [] then null else lib.concatStringsSep "," x;
-              description = ''
-                A list of Apache Kafka hosts to publish parsed reports
-                to.
-              '';
-            };
-
-            user = lib.mkOption {
-              type = with lib.types; nullOr str;
-              default = null;
-              description = ''
-                Username to use when connecting to Kafka, if
-                required.
-              '';
-            };
-
-            password = lib.mkOption {
-              type = with lib.types; nullOr path;
-              default = null;
-              description = ''
-                The path to a file containing the password to use when
-                connecting to Kafka, if required.
-              '';
-            };
-
-            ssl = lib.mkOption {
-              type = with lib.types; nullOr bool;
-              default = null;
-              description = ''
-                Whether to use an encrypted SSL/TLS connection.
-              '';
-            };
-
-            aggregate_topic = lib.mkOption {
-              type = with lib.types; nullOr str;
-              default = null;
-              example = "aggregate";
-              description = ''
-                The Kafka topic to publish aggregate reports on.
-              '';
-            };
-
-            forensic_topic = lib.mkOption {
-              type = with lib.types; nullOr str;
-              default = null;
-              example = "forensic";
-              description = ''
-                The Kafka topic to publish forensic reports on.
-              '';
-            };
-          };
-
         };
 
       };
@@ -404,21 +399,14 @@ in
         enable = cfg.provision.grafana.datasource || cfg.provision.grafana.dashboard;
         datasources =
           let
-            pkgVer = lib.getVersion config.services.elasticsearch.package;
-            esVersion =
-              if lib.versionOlder pkgVer "7" then
-                "60"
-              else if lib.versionOlder pkgVer "8" then
-                "70"
-              else
-                throw "When provisioning parsedmarc grafana datasources: unknown Elasticsearch version.";
+            esVersion = lib.getVersion config.services.elasticsearch.package;
           in
             lib.mkIf cfg.provision.grafana.datasource [
               {
                 name = "dmarc-ag";
                 type = "elasticsearch";
                 access = "proxy";
-                url = "localhost:9200";
+                url = "http://localhost:9200";
                 jsonData = {
                   timeField = "date_range";
                   inherit esVersion;
@@ -428,7 +416,7 @@ in
                 name = "dmarc-fo";
                 type = "elasticsearch";
                 access = "proxy";
-                url = "localhost:9200";
+                url = "http://localhost:9200";
                 jsonData = {
                   timeField = "date_range";
                   inherit esVersion;
@@ -467,12 +455,17 @@ in
         # lists, empty attrsets and null. This makes it possible to
         # list interesting options in `settings` without them always
         # ending up in the resulting config.
-        filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! builtins.elem v [ null [] {} ])) cfg.settings;
+        filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null [] {} ])) cfg.settings;
+
+        # Extract secrets (attributes set to an attrset with a
+        # "_secret" key) from the settings and generate the commands
+        # to run to perform the secret replacements.
+        secretPaths = lib.catAttrs "_secret" (lib.collect isSecret filteredConfig);
         parsedmarcConfig = ini.generate "parsedmarc.ini" filteredConfig;
-        mkSecretReplacement = file:
-          lib.optionalString (file != null) ''
-            replace-secret '${file}' '${file}' /run/parsedmarc/parsedmarc.ini
-          '';
+        mkSecretReplacement = file: ''
+          replace-secret ${lib.escapeShellArgs [ (hashString "sha256" file) file "/run/parsedmarc/parsedmarc.ini" ]}
+        '';
+        secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
       in
         {
           wantedBy = [ "multi-user.target" ];
@@ -487,10 +480,7 @@ in
                 umask u=rwx,g=,o=
                 cp ${parsedmarcConfig} /run/parsedmarc/parsedmarc.ini
                 chown parsedmarc:parsedmarc /run/parsedmarc/parsedmarc.ini
-                ${mkSecretReplacement cfg.settings.smtp.password}
-                ${mkSecretReplacement cfg.settings.imap.password}
-                ${mkSecretReplacement cfg.settings.elasticsearch.password}
-                ${mkSecretReplacement cfg.settings.kafka.password}
+                ${secretReplacements}
               '' + lib.optionalString cfg.provision.localMail.enable ''
                 openssl rand -hex 64 >/run/parsedmarc/dmarc_user_passwd
                 replace-secret '@imap-password@' '/run/parsedmarc/dmarc_user_passwd' /run/parsedmarc/parsedmarc.ini
@@ -504,7 +494,7 @@ in
             Group = "parsedmarc";
             DynamicUser = true;
             RuntimeDirectory = "parsedmarc";
-            RuntimeDirectoryMode = 0700;
+            RuntimeDirectoryMode = "0700";
             CapabilityBoundingSet = "";
             PrivateDevices = true;
             PrivateMounts = true;
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 1f396634ae01..0c0931d3d295 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -34,19 +34,19 @@ 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;
         default = pkgs.prometheus-alertmanager;
         defaultText = literalExpression "pkgs.alertmanager";
-        description = ''
+        description = lib.mdDoc ''
           Package that should be used for alertmanager.
         '';
       };
@@ -54,7 +54,7 @@ in {
       configuration = mkOption {
         type = types.nullOr types.attrs;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Alertmanager configuration as nix attribute set.
         '';
       };
@@ -62,7 +62,7 @@ in {
       configText = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Alertmanager configuration as YAML text. If non-null, this option
           defines the text that is written to alertmanager.yml. If null, the
           contents of alertmanager.yml is generated from the structured config
@@ -73,7 +73,7 @@ in {
       logFormat = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           If set use a syslog logger or JSON logging.
         '';
       };
@@ -81,7 +81,7 @@ in {
       logLevel = mkOption {
         type = types.enum ["debug" "info" "warn" "error" "fatal"];
         default = "warn";
-        description = ''
+        description = lib.mdDoc ''
           Only log messages with the given severity or above.
         '';
       };
@@ -89,7 +89,7 @@ in {
       webExternalUrl = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy).
           Used for generating relative and absolute links back to Alertmanager itself.
           If the URL has a path portion, it will be used to prefix all HTTP endoints served by Alertmanager.
@@ -100,16 +100,16 @@ in {
       listenAddress = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Address to listen on for the web interface and API. Empty string will listen on all interfaces.
           "localhost" will listen on 127.0.0.1 (but not ::1).
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 9093;
-        description = ''
+        description = lib.mdDoc ''
           Port to listen on for the web interface and API.
         '';
       };
@@ -117,7 +117,7 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open port in firewall for incoming connections.
         '';
       };
@@ -125,7 +125,7 @@ in {
       clusterPeers = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Initial peers for HA cluster.
         '';
       };
@@ -133,7 +133,7 @@ in {
       extraFlags = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Extra commandline options when launching the Alertmanager.
         '';
       };
@@ -142,11 +142,11 @@ in {
         type = types.nullOr types.path;
         default = null;
         example = "/root/alertmanager.env";
-        description = ''
+        description = lib.mdDoc ''
           File to load as environment file. Environment variables
           from this file will be interpolated into the config file
           using envsubst with this syntax:
-          <literal>$ENVIRONMENT ''${VARIABLE}</literal>
+          `$ENVIRONMENT ''${VARIABLE}`
         '';
       };
     };
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 52525e8935ba..f516b75ab10f 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -3,12 +3,14 @@
 with lib;
 
 let
+  yaml = pkgs.formats.yaml { };
   cfg = config.services.prometheus;
+  checkConfigEnabled =
+    (lib.isBool cfg.checkConfig && cfg.checkConfig)
+      || cfg.checkConfig == "syntax-only";
 
   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
@@ -26,7 +28,7 @@ let
 
   # a wrapper that verifies that the configuration is valid
   promtoolCheck = what: name: file:
-    if cfg.checkConfig then
+    if checkConfigEnabled then
       pkgs.runCommandLocal
         "${name}-${replaceStrings [" "] [""] what}-checked"
         { buildInputs = [ cfg.package ]; } ''
@@ -34,13 +36,7 @@ let
         promtool ${what} $out
       '' else file;
 
-  # Pretty-print JSON to a file
-  writePrettyJSON = name: x:
-    pkgs.runCommandLocal name { } ''
-      echo '${builtins.toJSON x}' | ${pkgs.jq}/bin/jq . > $out
-    '';
-
-  generatedPrometheusYml = writePrettyJSON "prometheus.yml" promConfig;
+  generatedPrometheusYml = yaml.generate "prometheus.yml" promConfig;
 
   # This becomes the main config file for Prometheus
   promConfig = {
@@ -63,7 +59,7 @@ let
           pkgs.writeText "prometheus.yml" cfg.configText
         else generatedPrometheusYml;
     in
-    promtoolCheck "check config" "prometheus.yml" yml;
+    promtoolCheck "check config ${lib.optionalString (cfg.checkConfig == "syntax-only") "--syntax-only"}" "prometheus.yml" yml;
 
   cmdlineArgs = cfg.extraFlags ++ [
     "--storage.tsdb.path=${workingDir}/data/"
@@ -75,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:
@@ -101,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 {
@@ -186,7 +183,7 @@ let
     options = {
       username = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           HTTP username
         '';
       };
@@ -253,13 +250,13 @@ 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.
         '';
       };
       job_name = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The job name assigned to scraped metrics by default.
         '';
       };
@@ -290,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
@@ -301,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.
       '';
 
@@ -325,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 ''
@@ -381,9 +378,8 @@ 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> for more detail.
+        See [the relevant Prometheus configuration docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#gce_sd_config)
+        for more detail.
       '';
 
       hetzner_sd_configs = mkOpt (types.listOf promTypes.hetzner_sd_config) ''
@@ -515,7 +511,7 @@ let
 
       subscription_id = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The subscription ID.
         '';
       };
@@ -594,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.
     '';
@@ -620,7 +616,7 @@ let
   mkDockerSdConfigModule = extraOptions: mkSdConfigModule ({
     host = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Address of the Docker daemon.
       '';
     };
@@ -635,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.
             '';
           };
@@ -667,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`.
       '';
     };
@@ -677,7 +673,7 @@ let
     options = {
       names = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           A list of DNS SRV record names to be queried.
         '';
       };
@@ -700,7 +696,7 @@ let
     options = {
       region = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The AWS Region. If blank, the region from the instance metadata is used.
         '';
       };
@@ -710,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 ''
@@ -741,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.
               '';
             };
@@ -750,7 +746,7 @@ let
             values = mkOption {
               type = types.listOf types.str;
               default = [ ];
-              description = ''
+              description = lib.mdDoc ''
                 Value of the filter.
               '';
             };
@@ -764,7 +760,7 @@ let
   promTypes.eureka_sd_config = mkSdConfigModule {
     server = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The URL to connect to the Eureka server.
       '';
     };
@@ -774,7 +770,7 @@ let
     options = {
       files = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Patterns for files from which target groups are extracted. Refer
           to the Prometheus documentation for permitted filename patterns
           and formats.
@@ -793,14 +789,14 @@ let
       # required configuration values for `gce_sd_config`.
       project = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The GCP Project.
         '';
       };
 
       zone = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The zone of the scrape targets. If you need multiple zones use multiple
           gce_sd_configs.
         '';
@@ -809,9 +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" ''
@@ -827,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>
       '';
     };
   };
@@ -835,9 +829,9 @@ let
   promTypes.hetzner_sd_config = mkSdConfigModule {
     role = mkOption {
       type = types.enum [ "robot" "hcloud" ];
-      description = ''
+      description = lib.mdDoc ''
         The Hetzner role of entities that should be discovered.
-        One of <literal>robot</literal> or <literal>hcloud</literal>.
+        One of `robot` or `hcloud`.
       '';
     };
 
@@ -854,7 +848,7 @@ let
     options = {
       url = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           URL from which the targets are fetched.
         '';
       };
@@ -891,7 +885,7 @@ let
 
     role = mkOption {
       type = types.enum [ "endpoints" "service" "pod" "node" "ingress" ];
-      description = ''
+      description = lib.mdDoc ''
         The Kubernetes role of entities that should be discovered.
         One of endpoints, service, pod, node, or ingress.
       '';
@@ -922,7 +916,7 @@ let
             options = {
               role = mkOption {
                 type = types.str;
-                description = ''
+                description = lib.mdDoc ''
                   Selector role
                 '';
               };
@@ -956,7 +950,7 @@ let
   promTypes.kuma_sd_config = mkSdConfigModule {
     server = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Address of the Kuma Control Plane's MADS xDS server.
       '';
     };
@@ -981,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 ''
@@ -1024,7 +1018,7 @@ let
   promTypes.marathon_sd_config = mkSdConfigModule {
     servers = mkOption {
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         List of URLs to be used to contact Marathon servers. You need to provide at least one server URL.
       '';
     };
@@ -1035,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.
     '';
   };
 
@@ -1050,14 +1044,14 @@ let
     options = {
       servers = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The Zookeeper servers.
         '';
       };
 
       paths = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Paths can point to a single service, or the root of a tree of services.
         '';
       };
@@ -1099,14 +1093,14 @@ let
       {
         role = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The OpenStack role of entities that should be discovered.
           '';
         };
 
         region = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The OpenStack Region.
           '';
         };
@@ -1167,14 +1161,14 @@ let
   promTypes.puppetdb_sd_config = mkSdConfigModule {
     url = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The URL of the PuppetDB root query endpoint.
       '';
     };
 
     query = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Puppet Query Language (PQL) query. Only resources are supported.
         https://puppet.com/docs/puppetdb/latest/api/query/v4/pql.html
       '';
@@ -1203,7 +1197,7 @@ let
     options = {
       access_key = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Access key to use. https://console.scaleway.com/project/credentials
         '';
       };
@@ -1220,14 +1214,14 @@ let
 
       project_id = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Project ID of the targets.
         '';
       };
 
       role = mkOption {
         type = types.enum [ "instance" "baremetal" ];
-        description = ''
+        description = lib.mdDoc ''
           Role of the targets to retrieve. Must be `instance` or `baremetal`.
         '';
       };
@@ -1277,7 +1271,7 @@ let
     options = {
       account = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The account to use for discovering new targets.
         '';
       };
@@ -1290,21 +1284,21 @@ let
 
       dns_suffix = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The DNS suffix which should be applied to target.
         '';
       };
 
       endpoint = mkOption {
         type = types.str;
-        description = ''
-          The Triton discovery endpoint (e.g. <literal>cmon.us-east-3b.triton.zone</literal>). This is
+        description = lib.mdDoc ''
+          The Triton discovery endpoint (e.g. `cmon.us-east-3b.triton.zone`). This is
           often the same value as dns_suffix.
         '';
       };
 
       groups = mkOpt (types.listOf types.str) ''
-        A list of groups for which targets are retrieved, only supported when targeting the <literal>container</literal> role.
+        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.
       '';
 
@@ -1329,21 +1323,21 @@ let
   promTypes.uyuni_sd_config = mkSdConfigModule {
     server = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The URL to connect to the Uyuni server.
       '';
     };
 
     username = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Credentials are used to authenticate the requests to Uyuni API.
       '';
     };
 
     password = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Credentials are used to authenticate the requests to Uyuni API.
       '';
     };
@@ -1365,14 +1359,14 @@ let
     options = {
       targets = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The targets specified by the target group.
         '';
       };
       labels = mkOption {
         type = types.attrsOf types.str;
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           Labels assigned to all metrics scraped from the targets.
         '';
       };
@@ -1428,7 +1422,7 @@ let
     options = {
       url = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           ServerName extension to indicate the name of the server.
           http://tools.ietf.org/html/rfc4366#section-3.1
         '';
@@ -1514,7 +1508,7 @@ let
     options = {
       url = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           ServerName extension to indicate the name of the server.
           http://tools.ietf.org/html/rfc4366#section-3.1
         '';
@@ -1568,19 +1562,13 @@ in
 
   options.services.prometheus = {
 
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable the Prometheus monitoring daemon.
-      '';
-    };
+    enable = mkEnableOption (lib.mdDoc "Prometheus monitoring daemon");
 
     package = mkOption {
       type = types.package;
       default = pkgs.prometheus;
       defaultText = literalExpression "pkgs.prometheus";
-      description = ''
+      description = lib.mdDoc ''
         The prometheus package that should be used.
       '';
     };
@@ -1588,7 +1576,7 @@ in
     port = mkOption {
       type = types.port;
       default = 9090;
-      description = ''
+      description = lib.mdDoc ''
         Port to listen on.
       '';
     };
@@ -1596,7 +1584,7 @@ in
     listenAddress = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = ''
+      description = lib.mdDoc ''
         Address to listen on for the web interface, API, and telemetry.
       '';
     };
@@ -1604,8 +1592,8 @@ in
     stateDir = mkOption {
       type = types.str;
       default = "prometheus2";
-      description = ''
-        Directory below <literal>/var/lib</literal> to store Prometheus metrics data.
+      description = lib.mdDoc ''
+        Directory below `/var/lib` to store Prometheus metrics data.
         This directory will be created automatically using systemd's StateDirectory mechanism.
       '';
     };
@@ -1613,7 +1601,7 @@ in
     extraFlags = mkOption {
       type = types.listOf types.str;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         Extra commandline options when launching Prometheus.
       '';
     };
@@ -1621,11 +1609,11 @@ in
     enableReload = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Reload prometheus when configuration file changes (instead of restart).
 
         The following property holds: switching to a configuration
-        (<literal>switch-to-configuration</literal>) that changes the prometheus
+        (`switch-to-configuration`) that changes the prometheus
         configuration only finishes successully when prometheus has finished
         loading the new configuration.
       '';
@@ -1634,7 +1622,7 @@ in
     configText = mkOption {
       type = types.nullOr types.lines;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         If non-null, this option defines the text that is written to
         prometheus.yml. If null, the contents of prometheus.yml is generated
         from the structured config options.
@@ -1644,7 +1632,7 @@ in
     globalConfig = mkOption {
       type = promTypes.globalConfig;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Parameters that are valid in all  configuration contexts. They
         also serve as defaults for other configuration sections
       '';
@@ -1653,25 +1641,25 @@ in
     remoteRead = mkOption {
       type = types.listOf promTypes.remote_read;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         Parameters of the endpoints to query from.
-        See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_read">the official documentation</link> for more information.
+        See [the official documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_read) for more information.
       '';
     };
 
     remoteWrite = mkOption {
       type = types.listOf promTypes.remote_write;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         Parameters of the endpoints to send samples to.
-        See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write">the official documentation</link> for more information.
+        See [the official documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write) for more information.
       '';
     };
 
     rules = mkOption {
       type = types.listOf types.str;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         Alerting and/or Recording rules to evaluate at runtime.
       '';
     };
@@ -1679,7 +1667,7 @@ in
     ruleFiles = mkOption {
       type = types.listOf types.path;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         Any additional rules files to include in this configuration.
       '';
     };
@@ -1687,7 +1675,7 @@ in
     scrapeConfigs = mkOption {
       type = types.listOf promTypes.scrape_config;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         A list of scrape configurations.
       '';
     };
@@ -1706,16 +1694,16 @@ in
         } ]
       '';
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         A list of alertmanagers to send alerts to.
-        See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alertmanager_config">the official documentation</link> for more information.
+        See [the official documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alertmanager_config) for more information.
       '';
     };
 
     alertmanagerNotificationQueueCapacity = mkOption {
       type = types.int;
       default = 10000;
-      description = ''
+      description = lib.mdDoc ''
         The capacity of the queue for pending alert manager notifications.
       '';
     };
@@ -1724,23 +1712,35 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "https://example.com/";
-      description = ''
+      description = lib.mdDoc ''
         The URL under which Prometheus is externally reachable (for example,
         if Prometheus is served via a reverse proxy).
       '';
     };
 
+    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 = types.bool;
+      type = with types; either bool (enum [ "syntax-only" ]);
       default = true;
-      description = ''
-        Check configuration with <literal>promtool
-        check</literal>. The call to <literal>promtool</literal> is
-        subject to sandboxing by Nix. When credentials are stored in
-        external files (<literal>password_file</literal>,
-        <literal>bearer_token_file</literal>, etc), they will not be
-        visible to <literal>promtool</literal> and it will report
-        errors, despite a correct configuration.
+      example = "syntax-only";
+      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
+        (`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 `"syntax-only"`
+        in order to only syntax check the Prometheus configuration.
       '';
     };
 
@@ -1748,7 +1748,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "15d";
-      description = ''
+      description = lib.mdDoc ''
         How long to retain samples in storage.
       '';
     };
@@ -1798,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/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 41302d6d3ceb..f3fbfb149ad7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -36,6 +36,7 @@ let
     "fastly"
     "fritzbox"
     "influxdb"
+    "ipmi"
     "json"
     "jitsi"
     "kea"
@@ -50,6 +51,7 @@ let
     "nginx"
     "nginxlog"
     "node"
+    "nut"
     "openldap"
     "openvpn"
     "pihole"
@@ -66,46 +68,49 @@ let
     "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;
-      description = ''
+      description = lib.mdDoc ''
         Port to listen on.
       '';
     };
     listenAddress = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = ''
+      description = lib.mdDoc ''
         Address to listen on.
       '';
     };
     extraFlags = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Extra commandline options to pass to the ${name} exporter.
       '';
     };
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Open port in firewall for incoming connections.
       '';
     };
@@ -115,23 +120,23 @@ 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 {
       type = types.str;
       default = "${name}-exporter";
-      description = ''
+      description = lib.mdDoc ''
         User name under which the ${name} exporter shall be run.
       '';
     };
     group = mkOption {
       type = types.str;
       default = "${name}-exporter";
-      description = ''
+      description = lib.mdDoc ''
         Group under which the ${name} exporter shall be run.
       '';
     };
@@ -194,7 +199,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,8 +231,12 @@ in
   options.services.prometheus.exporters = mkOption {
     type = types.submodule {
       options = (mkSubModules);
+      imports = [
+        ../../../misc/assertions.nix
+        (lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ])
+      ];
     };
-    description = "Prometheus exporter configuration";
+    description = lib.mdDoc "Prometheus exporter configuration";
     default = {};
     example = literalExpression ''
       {
@@ -242,6 +251,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 +298,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;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml
index 1df88bb61a12..e922e1ace8d0 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ b/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -37,7 +37,7 @@
    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
+   have to be configured separately 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
@@ -179,7 +179,7 @@ in
   # 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.
+  # service configuration.
   # Note that by default 'DynamicUser' is 'true'.
   serviceOpts = {
     serviceConfig = {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix b/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix
index 57c35a742c5f..a8a9f84ea8ea 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix
@@ -11,7 +11,7 @@ in
     apcupsdAddress = mkOption {
       type = types.str;
       default = ":3551";
-      description = ''
+      description = lib.mdDoc ''
         Address of the apcupsd Network Information Server (NIS).
       '';
     };
@@ -19,7 +19,7 @@ in
     apcupsdNetwork = mkOption {
       type = types.enum ["tcp" "tcp4" "tcp6"];
       default = "tcp";
-      description = ''
+      description = lib.mdDoc ''
         Network of the apcupsd Network Information Server (NIS): one of "tcp", "tcp4", or "tcp6".
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix b/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix
index 2adcecc728bd..bc67fe59b3b8 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix
@@ -11,14 +11,14 @@ in
     scrapeUri = mkOption {
       type = types.str;
       default = "http://localhost:8081/artifactory";
-      description = ''
+      description = lib.mdDoc ''
         URI on which to scrape JFrog Artifactory.
       '';
     };
 
     artiUsername = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Username for authentication against JFrog Artifactory API.
       '';
     };
@@ -26,7 +26,7 @@ in
     artiPassword = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Password for authentication against JFrog Artifactory API.
         One of the password or access token needs to be set.
       '';
@@ -35,7 +35,7 @@ in
     artiAccessToken = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Access token for authentication against JFrog Artifactory API.
         One of the password or access token needs to be set.
       '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
index 16c2920751d9..bd2003f06504 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
@@ -11,28 +11,28 @@ in
     bindURI = mkOption {
       type = types.str;
       default = "http://localhost:8053/";
-      description = ''
+      description = lib.mdDoc ''
         HTTP XML API address of an Bind server.
       '';
     };
     bindTimeout = mkOption {
       type = types.str;
       default = "10s";
-      description = ''
+      description = lib.mdDoc ''
         Timeout for trying to get stats from Bind.
       '';
     };
     bindVersion = mkOption {
       type = types.enum [ "xml.v2" "xml.v3" "auto" ];
       default = "auto";
-      description = ''
+      description = lib.mdDoc ''
         BIND statistics version. Can be detected automatically.
       '';
     };
     bindGroups = mkOption {
       type = types.listOf (types.enum [ "server" "view" "tasks" ]);
       default = [ "server" "view" ];
-      description = ''
+      description = lib.mdDoc ''
         List of statistics to collect. Available: [server, view, tasks]
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
index 5fda4c980ebb..5f6c36f4c567 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
@@ -11,21 +11,21 @@ in
     birdVersion = mkOption {
       type = types.enum [ 1 2 ];
       default = 2;
-      description = ''
+      description = lib.mdDoc ''
         Specifies whether BIRD1 or BIRD2 is in use.
       '';
     };
     birdSocket = mkOption {
       type = types.path;
       default = "/run/bird/bird.ctl";
-      description = ''
+      description = lib.mdDoc ''
         Path to BIRD2 (or BIRD1 v4) socket.
       '';
     };
     newMetricFormat = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Enable the new more-generic metric format.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix b/nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix
index 43721f70b499..330d54126448 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix
@@ -11,14 +11,14 @@ in
     rpcUser = mkOption {
       type = types.str;
       default = "bitcoinrpc";
-      description = ''
+      description = lib.mdDoc ''
         RPC user name.
       '';
     };
 
     rpcPasswordFile = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         File containing RPC password.
       '';
     };
@@ -26,7 +26,7 @@ in
     rpcScheme = mkOption {
       type = types.enum [ "http" "https" ];
       default = "http";
-      description = ''
+      description = lib.mdDoc ''
         Whether to connect to bitcoind over http or https.
       '';
     };
@@ -34,7 +34,7 @@ in
     rpcHost = mkOption {
       type = types.str;
       default = "localhost";
-      description = ''
+      description = lib.mdDoc ''
         RPC host.
       '';
     };
@@ -42,7 +42,7 @@ in
     rpcPort = mkOption {
       type = types.port;
       default = 8332;
-      description = ''
+      description = lib.mdDoc ''
         RPC port number.
       '';
     };
@@ -50,7 +50,7 @@ in
     refreshSeconds = mkOption {
       type = types.ints.unsigned;
       default = 300;
-      description = ''
+      description = lib.mdDoc ''
         How often to ask bitcoind for metrics.
       '';
     };
@@ -58,7 +58,7 @@ in
     extraEnv = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Extra environment variables for the exporter.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
index fe8d905da3fe..66eaed51d2ea 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -35,14 +35,14 @@ in {
   extraOpts = {
     configFile = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to configuration file.
       '';
     };
     enableConfigCheck = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to run a correctness check for the configuration file. This depends
         on the configuration file residing in the nix-store. Paths passed as string will
         be copied to the store.
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix b/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix
index e9be39608fcb..0515b72b13f9 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/buildkite-agent.nix
@@ -11,7 +11,7 @@ in
     tokenPath = mkOption {
       type = types.nullOr types.path;
       apply = final: if final == null then null else toString final;
-      description = ''
+      description = lib.mdDoc ''
         The token from your Buildkite "Agents" page.
 
         A run-time path to the token file, which is supposed to be provisioned
@@ -22,14 +22,14 @@ in
       type = types.str;
       default = "30s";
       example = "1min";
-      description = ''
+      description = lib.mdDoc ''
         How often to update metrics.
       '';
     };
     endpoint = mkOption {
       type = types.str;
       default = "https://agent.buildkite.com/v3";
-      description = ''
+      description = lib.mdDoc ''
         The Buildkite Agent API endpoint.
       '';
     };
@@ -37,7 +37,7 @@ in
       type = with types; nullOr (listOf str);
       default = null;
       example = literalExpression ''[ "my-queue1" "my-queue2" ]'';
-      description = ''
+      description = lib.mdDoc ''
         Which specific queues to process.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
index a7f4d3e096fe..0c2de683ecf7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
@@ -9,24 +9,24 @@ in
   port = 9103;
   extraOpts = {
     collectdBinary = {
-      enable = mkEnableOption "collectd binary protocol receiver";
+      enable = mkEnableOption (lib.mdDoc "collectd binary protocol receiver");
 
       authFile = mkOption {
         default = null;
         type = types.nullOr types.path;
-        description = "File mapping user names to pre-shared keys (passwords).";
+        description = lib.mdDoc "File mapping user names to pre-shared keys (passwords).";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 25826;
-        description = "Network address on which to accept collectd binary network packets.";
+        description = lib.mdDoc "Network address on which to accept collectd binary network packets.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           Address to listen on for binary network packets.
           '';
       };
@@ -34,7 +34,7 @@ in
       securityLevel = mkOption {
         type = types.enum ["None" "Sign" "Encrypt"];
         default = "None";
-        description = ''
+        description = lib.mdDoc ''
           Minimum required security level for accepted packets.
         '';
       };
@@ -44,7 +44,7 @@ in
       type = types.enum [ "logfmt" "json" ];
       default = "logfmt";
       example = "json";
-      description = ''
+      description = lib.mdDoc ''
         Set the log format.
       '';
     };
@@ -52,7 +52,7 @@ in
     logLevel = mkOption {
       type = types.enum ["debug" "info" "warn" "error" "fatal"];
       default = "info";
-      description = ''
+      description = lib.mdDoc ''
         Only log messages with the given severity or above.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix b/nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix
index 330610a15d9e..437cece588a7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix
@@ -24,28 +24,28 @@ in {
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           Hostname of IMAP server to connect to.
         '';
       };
       port = mkOption {
         type = types.port;
         default = 993;
-        description = ''
+        description = lib.mdDoc ''
           Port of the IMAP server to connect to.
         '';
       };
       username = mkOption {
         type = types.str;
         example = "postmaster@example.org";
-        description = ''
+        description = lib.mdDoc ''
           Login username for the IMAP connection.
         '';
       };
       passwordFile = mkOption {
         type = types.str;
         example = "/run/secrets/dovecot_pw";
-        description = ''
+        description = lib.mdDoc ''
           File containing the login password for the IMAP connection.
         '';
       };
@@ -54,21 +54,21 @@ in {
       inbox = mkOption {
         type = types.str;
         default = "INBOX";
-        description = ''
+        description = lib.mdDoc ''
           IMAP mailbox that is checked for incoming DMARC aggregate reports
         '';
       };
       done = mkOption {
         type = types.str;
         default = "Archive";
-        description = ''
+        description = lib.mdDoc ''
           IMAP mailbox that successfully processed reports are moved to.
         '';
       };
       error = mkOption {
         type = types.str;
         default = "Invalid";
-        description = ''
+        description = lib.mdDoc ''
           IMAP mailbox that emails are moved to that could not be processed.
         '';
       };
@@ -76,7 +76,7 @@ in {
     pollIntervalSeconds = mkOption {
       type = types.ints.unsigned;
       default = 60;
-      description = ''
+      description = lib.mdDoc ''
         How often to poll the IMAP server in seconds.
       '';
     };
@@ -84,7 +84,7 @@ in {
       type = types.ints.unsigned;
       default = 604800;
       defaultText = "7 days (in seconds)";
-      description = ''
+      description = lib.mdDoc ''
         How long individual report IDs will be remembered to avoid
         counting double delivered reports twice.
       '';
@@ -92,8 +92,8 @@ in {
     debug = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to declare enable <literal>--debug</literal>.
+      description = lib.mdDoc ''
+        Whether to declare enable `--debug`.
       '';
     };
   };
@@ -108,7 +108,7 @@ in {
           -i ${pkgs.writeText "dmarc-exporter.json.template" json} \
           -o ''${STATE_DIRECTORY}/dmarc-exporter.json
 
-        exec ${pkgs.prometheus-dmarc-exporter}/bin/prometheus-dmarc-exporter \
+        exec ${pkgs.dmarc-metrics-exporter}/bin/dmarc-metrics-exporter \
           --configuration /var/lib/prometheus-dmarc-exporter/dmarc-exporter.json \
           ${optionalString cfg.debug "--debug"}
       ''}";
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
index 68afba21d64a..ece42a34cb06 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
@@ -11,7 +11,7 @@ in
     dnsmasqListenAddress = mkOption {
       type = types.str;
       default = "localhost:53";
-      description = ''
+      description = lib.mdDoc ''
         Address on which dnsmasq listens.
       '';
     };
@@ -19,8 +19,8 @@ in
       type = types.path;
       default = "/var/lib/misc/dnsmasq.leases";
       example = "/var/lib/dnsmasq/dnsmasq.leases";
-      description = ''
-        Path to the <literal>dnsmasq.leases</literal> file.
+      description = lib.mdDoc ''
+        Path to the `dnsmasq.leases` file.
       '';
     };
   };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
index 092ac6fea7d7..6fb438353a4c 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -11,7 +11,7 @@ in
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
@@ -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,14 +60,14 @@ in
             }
           ''';
         }
-        </programlisting>
+        ```
       '';
     };
     scopes = mkOption {
       type = types.listOf types.str;
       default = [ "user" ];
       example = [ "user" "global" ];
-      description = ''
+      description = lib.mdDoc ''
         Stats scopes to query.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
index 55a61c4949ee..36409caccf2e 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix
@@ -7,14 +7,14 @@ 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;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to a fastly-exporter configuration file.
-        Example one can be generated with <literal>fastly-exporter --config-file-example</literal>.
+        Example one can be generated with `fastly-exporter --config-file-example`.
       '';
       example = "./fastly-exporter-config.txt";
     };
@@ -22,7 +22,7 @@ in
     tokenPath = mkOption {
       type = types.nullOr types.path;
       apply = final: if final == null then null else toString final;
-      description = ''
+      description = lib.mdDoc ''
         A run-time path to the token file, which is supposed to be provisioned
         outside of Nix store.
       '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/flow.nix b/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
index b85e5461f218..81099aaf1704 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/flow.nix
@@ -10,19 +10,19 @@ in {
     brokers = mkOption {
       type = types.listOf types.str;
       example = literalExpression ''[ "kafka.example.org:19092" ]'';
-      description = "List of Kafka brokers to connect to.";
+      description = lib.mdDoc "List of Kafka brokers to connect to.";
     };
 
     asn = mkOption {
       type = types.ints.positive;
       example = 65542;
-      description = "The ASN being monitored.";
+      description = lib.mdDoc "The ASN being monitored.";
     };
 
     partitions = mkOption {
       type = types.listOf types.int;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         The number of the partitions to consume, none means all.
       '';
     };
@@ -30,7 +30,7 @@ in {
     topic = mkOption {
       type = types.str;
       example = "pmacct.acct";
-      description = "The Kafka topic to consume from.";
+      description = lib.mdDoc "The Kafka topic to consume from.";
     };
   };
 
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
index 9526597b8c96..dc53d21406ff 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
@@ -11,7 +11,7 @@ in
     gatewayAddress = mkOption {
       type = types.str;
       default = "fritz.box";
-      description = ''
+      description = lib.mdDoc ''
         The hostname or IP of the FRITZ!Box.
       '';
     };
@@ -19,7 +19,7 @@ in
     gatewayPort = mkOption {
       type = types.int;
       default = 49000;
-      description = ''
+      description = lib.mdDoc ''
         The port of the FRITZ!Box UPnP service.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix b/nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix
index ba45173e946a..61c0c08d2250 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix
@@ -12,13 +12,13 @@ in
       type = types.str;
       default = "5m";
       example = "10m";
-      description = "How long a sample is valid for";
+      description = lib.mdDoc "How long a sample is valid for";
     };
     udpBindAddress = mkOption {
       type = types.str;
       default = ":9122";
       example = "192.0.2.1:9122";
-      description = "Address on which to listen for udp packets";
+      description = lib.mdDoc "Address on which to listen for udp packets";
     };
   };
   serviceOpts = {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/ipmi.nix b/nixos/modules/services/monitoring/prometheus/exporters/ipmi.nix
new file mode 100644
index 000000000000..55c4f4aa4826
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix b/nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix
index c93a8f98e552..024602718602 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix
@@ -11,7 +11,7 @@ in
     url = mkOption {
       type = types.str;
       default = "http://localhost:8080/colibri/stats";
-      description = ''
+      description = lib.mdDoc ''
         Jitsi Videobridge metrics URL to monitor.
         This is usually /colibri/stats on port 8080 of the jitsi videobridge host.
       '';
@@ -20,7 +20,7 @@ in
       type = types.str;
       default = "30s";
       example = "1min";
-      description = ''
+      description = lib.mdDoc ''
         How often to scrape new data
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
index 1800da69a255..473f3a7e47e3 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/json.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
@@ -10,7 +10,7 @@ in
   extraOpts = {
     configFile = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to configuration file.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
index e0ee90d9b97d..ed33c72f644f 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
@@ -19,7 +19,7 @@ in {
           "/run/kea/kea-dhcp6.socket"
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Paths to kea control sockets
       '';
     };
@@ -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/nixos/modules/services/monitoring/prometheus/exporters/knot.nix b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
index 29e543f1013b..a73425b37da7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
@@ -11,24 +11,23 @@ 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`.
       '';
     };
 
     knotSocketPath = mkOption {
       type = types.str;
       default = "/run/knot/knot.sock";
-      description = ''
-        Socket path of <citerefentry><refentrytitle>knotd</refentrytitle>
-        <manvolnum>8</manvolnum></citerefentry>.
+      description = lib.mdDoc ''
+        Socket path of {manpage}`knotd(8)`.
       '';
     };
 
     knotSocketTimeout = mkOption {
       type = types.int;
       default = 2000;
-      description = ''
+      description = lib.mdDoc ''
         Timeout in seconds.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix b/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix
index 35f972020574..9f914b1dc146 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix
@@ -11,21 +11,21 @@ in
     lndHost = mkOption {
       type = types.str;
       default = "localhost:10009";
-      description = ''
+      description = lib.mdDoc ''
         lnd instance gRPC address:port.
       '';
     };
 
     lndTlsPath = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to lnd TLS certificate.
       '';
     };
 
     lndMacaroonDir = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to lnd macaroons.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
index 956bd96aa454..15079f5841f4 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
@@ -5,6 +5,8 @@ with lib;
 let
   cfg = config.services.prometheus.exporters.mail;
 
+  configFile = if cfg.configuration != null then configurationFile else (escapeShellArg cfg.configFile);
+
   configurationFile = pkgs.writeText "prometheus-mail-exporter.conf" (builtins.toJSON (
     # removes the _module attribute, null values and converts attrNames to lowercase
     mapAttrs' (name: value:
@@ -20,41 +22,41 @@ let
   serverOptions.options = {
     name = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Value for label 'configname' which will be added to all metrics.
       '';
     };
     server = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Hostname of the server that should be probed.
       '';
     };
     port = mkOption {
-      type = types.int;
+      type = types.port;
       example = 587;
-      description = ''
+      description = lib.mdDoc ''
         Port to use for SMTP.
       '';
     };
     from = mkOption {
       type = types.str;
       example = "exporteruser@domain.tld";
-      description = ''
+      description = lib.mdDoc ''
         Content of 'From' Header for probing mails.
       '';
     };
     to = mkOption {
       type = types.str;
       example = "exporteruser@domain.tld";
-      description = ''
+      description = lib.mdDoc ''
         Content of 'To' Header for probing mails.
       '';
     };
     detectionDir = mkOption {
       type = types.path;
       example = "/var/spool/mail/exporteruser/new";
-      description = ''
+      description = lib.mdDoc ''
         Directory in which new mails for the exporter user are placed.
         Note that this needs to exist when the exporter starts.
       '';
@@ -63,14 +65,14 @@ let
       type = types.nullOr types.str;
       default = null;
       example = "exporteruser@domain.tld";
-      description = ''
+      description = lib.mdDoc ''
         Username to use for SMTP authentication.
       '';
     };
     passphrase = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Password to use for SMTP authentication.
       '';
     };
@@ -80,20 +82,20 @@ let
     monitoringInterval = mkOption {
       type = types.str;
       example = "10s";
-      description = ''
+      description = lib.mdDoc ''
         Time interval between two probe attempts.
       '';
     };
     mailCheckTimeout = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Timeout until mails are considered "didn't make it".
       '';
     };
     disableFileDeletion = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Disables the exporter's function to delete probing mails.
       '';
     };
@@ -110,17 +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";
@@ -129,7 +130,7 @@ let
             }
           ''';
         }
-        </programlisting>
+        ```
       '';
     };
   };
@@ -137,24 +138,31 @@ in
 {
   port = 9225;
   extraOpts = {
+    environmentFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc ''
+        File containing env-vars to be substituted into the exporter's config.
+      '';
+    };
     configFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Specify the mailexporter configuration file to use.
       '';
     };
     configuration = mkOption {
       type = types.nullOr (types.submodule exporterOptions);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Specify the mailexporter configuration file to use.
       '';
     };
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
@@ -162,13 +170,19 @@ in
   serviceOpts = {
     serviceConfig = {
       DynamicUser = false;
+      EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+      RuntimeDirectory = "prometheus-mail-exporter";
+      ExecStartPre = [
+        "${pkgs.writeShellScript "subst-secrets-mail-exporter" ''
+          umask 0077
+          ${pkgs.envsubst}/bin/envsubst -i ${configFile} -o ''${RUNTIME_DIRECTORY}/mail-exporter.json
+        ''}"
+      ];
       ExecStart = ''
         ${pkgs.prometheus-mail-exporter}/bin/mailexporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
           --web.telemetry-path ${cfg.telemetryPath} \
-          --config.file ${
-            if cfg.configuration != null then configurationFile else (escapeShellArg cfg.configFile)
-          } \
+          --config.file ''${RUNTIME_DIRECTORY}/mail-exporter.json \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix b/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
index 8f9536b702a5..54dab4b5581a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
@@ -11,9 +11,9 @@ in
     configFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to a mikrotik exporter configuration file. Mutually exclusive with
-        <option>configuration</option> option.
+        {option}`configuration` option.
       '';
       example = literalExpression "./mikrotik.yml";
     };
@@ -21,11 +21,11 @@ in
     configuration = mkOption {
       type = types.nullOr types.attrs;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Mikrotik exporter configuration as nix attribute set. Mutually exclusive with
-        <option>configFile</option> option.
+        {option}`configFile` option.
 
-        See <link xlink:href="https://github.com/nshttpd/mikrotik-exporter/blob/master/README.md"/>
+        See <https://github.com/nshttpd/mikrotik-exporter/blob/master/README.md>
         for the description of the configuration file format.
       '';
       example = literalExpression ''
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
index d6dd62f871bd..82cc3fc314f2 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
@@ -11,7 +11,7 @@ in
     minioAddress = mkOption {
       type = types.str;
       example = "https://10.0.0.1:9000";
-      description = ''
+      description = lib.mdDoc ''
         The URL of the minio server.
         Use HTTPS if Minio accepts secure connections only.
         By default this connects to the local minio server if enabled.
@@ -21,28 +21,28 @@ in
     minioAccessKey = mkOption {
       type = types.str;
       example = "yourMinioAccessKey";
-      description = ''
+      description = lib.mdDoc ''
         The value of the Minio access key.
         It is required in order to connect to the server.
         By default this uses the one from the local minio server if enabled
-        and <literal>config.services.minio.accessKey</literal>.
+        and `config.services.minio.accessKey`.
       '';
     };
 
     minioAccessSecret = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The value of the Minio access secret.
         It is required in order to connect to the server.
         By default this uses the one from the local minio server if enabled
-        and <literal>config.services.minio.secretKey</literal>.
+        and `config.services.minio.secretKey`.
       '';
     };
 
     minioBucketStats = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Collect statistics about the buckets and files in buckets.
         It requires more computation, use it carefully in case of large buckets..
       '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix b/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
index afd03f6c270e..222ea3e5384f 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/modemmanager.nix
@@ -11,7 +11,7 @@ in
     refreshRate = mkOption {
       type = types.str;
       default = "5s";
-      description = ''
+      description = lib.mdDoc ''
         How frequently ModemManager will refresh the extended signal quality
         information for each modem. The duration should be specified in seconds
         ("5s"), minutes ("1m"), or hours ("1h").
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
index ce7125bf5a83..7808c8861a76 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
@@ -11,16 +11,16 @@ in
     url = mkOption {
       type = types.str;
       example = "https://domain.tld";
-      description = ''
+      description = lib.mdDoc ''
         URL to the Nextcloud serverinfo page.
         Adding the path to the serverinfo API is optional, it defaults
-        to <literal>/ocs/v2.php/apps/serverinfo/api/v1/info</literal>.
+        to `/ocs/v2.php/apps/serverinfo/api/v1/info`.
       '';
     };
     username = mkOption {
       type = types.str;
       default = "nextcloud-exporter";
-      description = ''
+      description = lib.mdDoc ''
         Username for connecting to Nextcloud.
         Note that this account needs to have admin privileges in Nextcloud.
       '';
@@ -28,7 +28,7 @@ in
     passwordFile = mkOption {
       type = types.path;
       example = "/path/to/password-file";
-      description = ''
+      description = lib.mdDoc ''
         File containing the password for connecting to Nextcloud.
         Make sure that this file is readable by the exporter user.
       '';
@@ -36,7 +36,7 @@ in
     timeout = mkOption {
       type = types.str;
       default = "5s";
-      description = ''
+      description = lib.mdDoc ''
         Timeout for getting server info document.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 6f69f5919d1e..3158e71f0468 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -11,7 +11,7 @@ in
     scrapeUri = mkOption {
       type = types.str;
       default = "http://localhost/nginx_status";
-      description = ''
+      description = lib.mdDoc ''
         Address to access the nginx status page.
         Can be enabled with services.nginx.statusPage = true.
       '';
@@ -19,14 +19,14 @@ in
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
     sslVerify = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to perform certificate verification for https.
       '';
     };
@@ -37,7 +37,7 @@ in
         "label1=value1"
         "label2=value2"
       ];
-      description = ''
+      description = lib.mdDoc ''
         A list of constant labels that will be used in every metric.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
index 8c1f552d58a7..674dc9dd4158 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
@@ -10,21 +10,21 @@ 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.
       '';
     };
 
     metricsEndpoint = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
index 5e5fc7cd5524..dd8602e2c63d 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/node.nix
+++ b/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;
@@ -12,7 +14,7 @@ in
       type = types.listOf types.str;
       default = [];
       example = [ "systemd" ];
-      description = ''
+      description = lib.mdDoc ''
         Collectors to enable. The collectors listed here are enabled in addition to the default ones.
       '';
     };
@@ -20,7 +22,7 @@ in
       type = types.listOf types.str;
       default = [];
       example = [ "timex" ];
-      description = ''
+      description = lib.mdDoc ''
         Collectors to disable which are enabled by default.
       '';
     };
@@ -35,15 +37,17 @@ 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/nixos/modules/services/monitoring/prometheus/exporters/nut.nix b/nixos/modules/services/monitoring/prometheus/exporters/nut.nix
new file mode 100644
index 000000000000..1c86b48b4509
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix b/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix
index 888611ee6fa1..aee3ae5bb2d4 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix
@@ -10,37 +10,37 @@ 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 {
       default = "/metrics";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         URL path where metrics should be exposed.
       '';
     };
@@ -48,7 +48,7 @@ in {
       default = "30s";
       type = types.str;
       example = "1m";
-      description = ''
+      description = lib.mdDoc ''
         Scrape interval of the exporter.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/openvpn.nix b/nixos/modules/services/monitoring/prometheus/exporters/openvpn.nix
index a97a753ebc37..5b54dad99805 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/openvpn.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/openvpn.nix
@@ -9,15 +9,15 @@ in {
   extraOpts = {
     statusPaths = mkOption {
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         Paths to OpenVPN status files. Please configure the OpenVPN option
-        <literal>status</literal> accordingly.
+        `status` accordingly.
       '';
     };
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix b/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
index 4bc27ebc32f8..537d72e85c8f 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pihole.nix
@@ -12,7 +12,7 @@ in
       type = types.str;
       default = "";
       example = "580a770cb40511eb85290242ac130003580a770cb40511eb85290242ac130003";
-      description = ''
+      description = lib.mdDoc ''
         pi-hole API token which can be used instead of a password
       '';
     };
@@ -20,7 +20,7 @@ in
       type = types.str;
       default = "10s";
       example = "30s";
-      description = ''
+      description = lib.mdDoc ''
         How often to scrape new data
       '';
     };
@@ -28,7 +28,7 @@ in
       type = types.str;
       default = "";
       example = "password";
-      description = ''
+      description = lib.mdDoc ''
         The password to login into pihole. An api token can be used instead.
       '';
     };
@@ -36,7 +36,7 @@ in
       type = types.str;
       default = "pihole";
       example = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         Hostname or address where to find the pihole webinterface
       '';
     };
@@ -44,7 +44,7 @@ in
       type = types.port;
       default = 80;
       example = 443;
-      description = ''
+      description = lib.mdDoc ''
         The port pihole webinterface is reachable on
       '';
     };
@@ -52,7 +52,7 @@ in
       type = types.enum [ "http" "https" ];
       default = "http";
       example = "https";
-      description = ''
+      description = lib.mdDoc ''
         The protocol which is used to connect to pihole
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
index 4d3c1fa267e5..9f402b123110 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
@@ -10,17 +10,17 @@ in
   extraOpts = {
     group = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Group under which the postfix exporter shall be run.
         It should match the group that is allowed to access the
-        <literal>showq</literal> socket in the <literal>queue/public/</literal> directory.
-        Defaults to <literal>services.postfix.setgidGroup</literal> when postfix is enabled.
+        `showq` socket in the `queue/public/` directory.
+        Defaults to `services.postfix.setgidGroup` when postfix is enabled.
       '';
     };
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
@@ -28,7 +28,7 @@ in
       type = types.path;
       default = "/var/log/postfix_exporter_input.log";
       example = "/var/log/mail.log";
-      description = ''
+      description = lib.mdDoc ''
         Path where Postfix writes log entries.
         This file will be truncated by this exporter!
       '';
@@ -37,7 +37,7 @@ in
       type = types.path;
       default = "/var/lib/postfix/queue/public/showq";
       example = "/var/spool/postfix/public/showq";
-      description = ''
+      description = lib.mdDoc ''
         Path where Postfix places its showq socket.
       '';
     };
@@ -45,40 +45,42 @@ in
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable reading metrics from the systemd journal instead of from a logfile
         '';
       };
       unit = mkOption {
         type = types.str;
         default = "postfix.service";
-        description = ''
+        description = lib.mdDoc ''
           Name of the postfix systemd unit.
         '';
       };
       slice = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Name of the postfix systemd slice.
-          This overrides the <option>systemd.unit</option>.
+          This overrides the {option}`systemd.unit`.
         '';
       };
       journalPath = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to the systemd journal.
         '';
       };
     };
   };
   serviceOpts = {
+    after = mkIf cfg.systemd.enable [ cfg.systemd.unit ];
     serviceConfig = {
       DynamicUser = false;
       # By default, each prometheus exporter only gets AF_INET & AF_INET6,
       # but AF_UNIX is needed to read from the `showq`-socket.
       RestrictAddressFamilies = [ "AF_UNIX" ];
+      SupplementaryGroups = mkIf cfg.systemd.enable [ "systemd-journal" ];
       ExecStart = ''
         ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
index 3f9a32ef3995..755d771ecdff 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
@@ -11,7 +11,7 @@ in
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
@@ -19,14 +19,14 @@ in
       type = types.str;
       default = "user=postgres database=postgres host=/run/postgresql sslmode=disable";
       example = "postgresql://username:password@localhost:5432/postgres?sslmode=disable";
-      description = ''
+      description = lib.mdDoc ''
         Accepts PostgreSQL URI form and key=value form arguments.
       '';
     };
     runAsLocalSuperUser = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to run the exporter as the local 'postgres' super user.
       '';
     };
@@ -36,10 +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
@@ -48,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
@@ -56,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/nixos/modules/services/monitoring/prometheus/exporters/process.nix b/nixos/modules/services/monitoring/prometheus/exporters/process.nix
index 1e9c402fb55b..278d6cd78074 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/process.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/process.nix
@@ -18,11 +18,11 @@ in
           { name = "{{.Matches.Wrapped}} {{ .Matches.Args }}"; cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ]; }
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         All settings expressed as an Nix attrset.
 
         Check the official documentation for the corresponding YAML
-        settings that can all be used here: <link xlink:href="https://github.com/ncabatoff/process-exporter" />
+        settings that can all be used here: <https://github.com/ncabatoff/process-exporter>
       '';
     };
   };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
index ef708414c95e..e02acad3ecd1 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
+++ b/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;
@@ -20,7 +20,7 @@ in
       default = pkgs.prometheus-pve-exporter;
       defaultText = literalExpression "pkgs.prometheus-pve-exporter";
       example = literalExpression "pkgs.prometheus-pve-exporter";
-      description = ''
+      description = lib.mdDoc ''
         The package to use for prometheus-pve-exporter
       '';
     };
@@ -29,7 +29,7 @@ in
       type = with types; nullOr path;
       default = null;
       example = "/etc/prometheus-pve-exporter/pve.env";
-      description = ''
+      description = lib.mdDoc ''
         Path to the service's environment file. This path can either be a computed path in /nix/store or a path in the local filesystem.
 
         The environment file should NOT be stored in /nix/store as it contains passwords and/or keys in plain text.
@@ -42,7 +42,7 @@ in
       type = with types; nullOr path;
       default = null;
       example = "/etc/prometheus-pve-exporter/pve.yml";
-      description = ''
+      description = lib.mdDoc ''
         Path to the service's config file. This path can either be a computed path in /nix/store or a path in the local filesystem.
 
         The config file should NOT be stored in /nix/store as it will contain passwords and/or keys in plain text.
@@ -57,42 +57,42 @@ in
       status = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Collect Node/VM/CT status
         '';
       };
       version = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Collect PVE version info
         '';
       };
       node = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Collect PVE node info
         '';
       };
       cluster = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Collect PVE cluster info
         '';
       };
       resources = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Collect PVE resources info
         '';
       };
       config = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Collect PVE onboot status
         '';
       };
@@ -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/nixos/modules/services/monitoring/prometheus/exporters/py-air-control.nix b/nixos/modules/services/monitoring/prometheus/exporters/py-air-control.nix
index d9ab99221d9d..f03b3c4df916 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/py-air-control.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/py-air-control.nix
@@ -14,14 +14,14 @@ in
     deviceHostname = mkOption {
       type = types.str;
       example = "192.168.1.123";
-      description = ''
+      description = lib.mdDoc ''
         The hostname of the air purification device from which to scrape the metrics.
       '';
     };
     protocol = mkOption {
       type = types.str;
       default = "http";
-      description = ''
+      description = lib.mdDoc ''
         The protocol to use when communicating with the air purification device.
         Available: [http, coap, plain_coap]
       '';
@@ -29,8 +29,8 @@ in
     stateDir = mkOption {
       type = types.str;
       default = "prometheus-py-air-control-exporter";
-      description = ''
-        Directory below <literal>/var/lib</literal> to store runtime data.
+      description = lib.mdDoc ''
+        Directory below `/var/lib` to store runtime data.
         This directory will be created automatically using systemd's StateDirectory mechanism.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix b/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
index ed985751e428..0b48827f43f7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
@@ -69,7 +69,7 @@ in
           custom_label = "some_value";
         }
       '';
-      description = "Set of labels added to each metric.";
+      description = lib.mdDoc "Set of labels added to each metric.";
     };
   };
   serviceOpts.serviceConfig.ExecStart = ''
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix b/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
index ef829a1b7d02..1f7235cb7830 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/rtl_433.nix
@@ -12,15 +12,15 @@ in
         options = {
           name = lib.mkOption {
             type = str;
-            description = "Name to match.";
+            description = lib.mdDoc "Name to match.";
           };
           "${field}" = lib.mkOption {
             type = int;
-            inherit description;
+            description = lib.mdDoc description;
           };
           location = lib.mkOption {
             type = str;
-            description = "Location to match.";
+            description = lib.mdDoc "Location to match.";
           };
         };
       });
@@ -30,9 +30,9 @@ in
       type = lib.types.str;
       default = "-C si";
       example = "-C si -R 19";
-      description = ''
+      description = lib.mdDoc ''
         Flags passed verbatim to rtl_433 binary.
-        Having <literal>-C si</literal> (the default) is recommended since only Celsius temperatures are parsed.
+        Having `-C si` (the default) is recommended since only Celsius temperatures are parsed.
       '';
     };
     channels = lib.mkOption {
@@ -41,7 +41,7 @@ in
       example = [
         { name = "Acurite"; channel = 6543; location = "Kitchen"; }
       ];
-      description = ''
+      description = lib.mdDoc ''
         List of channel matchers to export.
       '';
     };
@@ -51,7 +51,7 @@ in
       example = [
         { name = "Nexus"; id = 1; location = "Bedroom"; }
       ];
-      description = ''
+      description = lib.mdDoc ''
         List of ID matchers to export.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/script.nix b/nixos/modules/services/monitoring/prometheus/exporters/script.nix
index a805a0ad335d..eab0e1d8a6b5 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/script.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/script.nix
@@ -15,18 +15,18 @@ in
           name = mkOption {
             type = str;
             example = "sleep";
-            description = "Name of the script.";
+            description = lib.mdDoc "Name of the script.";
           };
           script = mkOption {
             type = str;
             example = "sleep 5";
-            description = "Shell script to execute when metrics are requested.";
+            description = lib.mdDoc "Shell script to execute when metrics are requested.";
           };
           timeout = mkOption {
             type = nullOr int;
             default = null;
             example = 60;
-            description = "Optional timeout for the script in seconds.";
+            description = lib.mdDoc "Optional timeout for the script in seconds.";
           };
         };
       });
@@ -37,11 +37,11 @@ in
           ];
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         All settings expressed as an Nix attrset.
 
         Check the official documentation for the corresponding YAML
-        settings that can all be used here: <link xlink:href="https://github.com/adhocteam/script_exporter#sample-configuration" />
+        settings that can all be used here: <https://github.com/adhocteam/script_exporter#sample-configuration>
       '';
     };
   };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix b/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
index bac98364538d..0c5648c14149 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
+++ b/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 = concatStrings [
+    "--web.listen-address=\"${cfg.listenAddress}:${toString cfg.port}\" "
+    "--smartctl.path=\"${pkgs.smartmontools}/bin/smartctl\" "
+    "--smartctl.interval=\"${cfg.maxInterval}\" "
+    "${concatMapStringsSep " " (device: "--smartctl.device=${device}") cfg.devices}"
+  ];
 in {
   port = 9633;
 
@@ -24,7 +20,7 @@ in {
       example = literalExpression ''
         [ "/dev/sda", "/dev/nvme0n1" ];
       '';
-      description = ''
+      description = lib.mdDoc ''
         Paths to the disks that will be monitored. Will autodiscover
         all disks if none given.
       '';
@@ -33,7 +29,7 @@ in {
       type = types.str;
       default = "60s";
       example = "2m";
-      description = ''
+      description = lib.mdDoc ''
         Interval that limits how often a disk can be queried.
       '';
     };
@@ -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/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix b/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
index 0181c341a7ef..459f5842f546 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/smokeping.nix
@@ -17,27 +17,27 @@ in
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
     pingInterval = mkOption {
       type = goDuration;
       default = "1s";
-      description = ''
+      description = lib.mdDoc ''
         Interval between pings.
       '';
     };
     buckets = mkOption {
       type = types.commas;
       default = "5e-05,0.0001,0.0002,0.0004,0.0008,0.0016,0.0032,0.0064,0.0128,0.0256,0.0512,0.1024,0.2048,0.4096,0.8192,1.6384,3.2768,6.5536,13.1072,26.2144";
-      description = ''
+      description = lib.mdDoc ''
         List of buckets to use for the response duration histogram.
       '';
     };
     hosts = mkOption {
       type = with types; listOf str;
-      description = ''
+      description = lib.mdDoc ''
         List of endpoints to probe.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index de42663e67f4..edc6e4b5022a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -11,7 +11,7 @@ in
     configurationPath = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to a snmp exporter configuration file. Mutually exclusive with 'configuration' option.
       '';
       example = literalExpression "./snmp.yml";
@@ -20,7 +20,7 @@ in
     configuration = mkOption {
       type = types.nullOr types.attrs;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Snmp exporter configuration as nix attribute set. Mutually exclusive with 'configurationPath' option.
       '';
       example = {
@@ -36,7 +36,7 @@ in
     logFormat = mkOption {
       type = types.enum ["logfmt" "json"];
       default = "logfmt";
-      description = ''
+      description = lib.mdDoc ''
         Output format of log messages.
       '';
     };
@@ -44,7 +44,7 @@ in
     logLevel = mkOption {
       type = types.enum ["debug" "info" "warn" "error"];
       default = "info";
-      description = ''
+      description = lib.mdDoc ''
         Only log messages with the given severity or above.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/sql.nix b/nixos/modules/services/monitoring/prometheus/exporters/sql.nix
index 3496fd9541f3..678bc348679d 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/sql.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/sql.nix
@@ -7,7 +7,7 @@ let
       jobs = mkOption {
         type = attrsOf (submodule jobOptions);
         default = { };
-        description = "An attrset of metrics scraping jobs to run.";
+        description = lib.mdDoc "An attrset of metrics scraping jobs to run.";
       };
     };
   };
@@ -15,23 +15,23 @@ let
     options = with types; {
       interval = mkOption {
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           How often to run this job, specified in
-          <link xlink:href="https://golang.org/pkg/time/#ParseDuration">Go duration</link> format.
+          [Go duration](https://golang.org/pkg/time/#ParseDuration) format.
         '';
       };
       connections = mkOption {
         type = listOf str;
-        description = "A list of connection strings of the SQL servers to scrape metrics from";
+        description = lib.mdDoc "A list of connection strings of the SQL servers to scrape metrics from";
       };
       startupSql = mkOption {
         type = listOf str;
         default = [];
-        description = "A list of SQL statements to execute once after making a connection.";
+        description = lib.mdDoc "A list of SQL statements to execute once after making a connection.";
       };
       queries = mkOption {
         type = attrsOf (submodule queryOptions);
-        description = "SQL queries to run.";
+        description = lib.mdDoc "SQL queries to run.";
       };
     };
   };
@@ -40,20 +40,20 @@ let
       help = mkOption {
         type = nullOr str;
         default = null;
-        description = "A human-readable description of this metric.";
+        description = lib.mdDoc "A human-readable description of this metric.";
       };
       labels = mkOption {
         type = listOf str;
         default = [ ];
-        description = "A set of columns that will be used as Prometheus labels.";
+        description = lib.mdDoc "A set of columns that will be used as Prometheus labels.";
       };
       query = mkOption {
         type = str;
-        description = "The SQL query to run.";
+        description = lib.mdDoc "The SQL query to run.";
       };
       values = mkOption {
         type = listOf str;
-        description = "A set of columns that will be used as values of this metric.";
+        description = lib.mdDoc "A set of columns that will be used as values of this metric.";
       };
     };
   };
@@ -77,14 +77,14 @@ in
     configFile = mkOption {
       type = with types; nullOr path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to configuration file.
       '';
     };
     configuration = mkOption {
       type = with types; nullOr (submodule cfgOptions);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Exporter configuration as nix attribute set. Mutually exclusive with 'configFile' option.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix b/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix
new file mode 100644
index 000000000000..d9d732d8c125
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
index 81c5c70ed93f..b1d6760b40b3 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
@@ -11,7 +11,7 @@ in
     modemAddress = mkOption {
       type = types.str;
       default = "192.168.100.1";
-      description = ''
+      description = lib.mdDoc ''
         The hostname or IP of the cable modem.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
index 36c473677efa..7a9167110a27 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -11,15 +11,15 @@ in
     torControlAddress = mkOption {
       type = types.str;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         Tor control IP address or hostname.
       '';
     };
 
     torControlPort = mkOption {
-      type = types.int;
+      type = types.port;
       default = 9051;
-      description = ''
+      description = lib.mdDoc ''
         Tor control port.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix b/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix
index cf0efddd340a..f52d92a73d5d 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/unbound.nix
@@ -12,7 +12,7 @@ in
       # TODO: add shm when upstream implemented it
       type = types.enum [ "tcp" "uds" ];
       default = "uds";
-      description = ''
+      description = lib.mdDoc ''
         Which methods the exporter uses to get the information from unbound.
       '';
     };
@@ -20,7 +20,7 @@ in
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
@@ -29,7 +29,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "/run/unbound/unbound.socket";
-      description = ''
+      description = lib.mdDoc ''
         Path to the unbound socket for uds mode or the control interface port for tcp mode.
 
         Example:
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix b/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix
deleted file mode 100644
index 394e6e201f03..000000000000
--- a/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/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
index 8d0e8764001c..70f26d9783be 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
@@ -11,7 +11,7 @@ in
     unifiAddress = mkOption {
       type = types.str;
       example = "https://10.0.0.1:8443";
-      description = ''
+      description = lib.mdDoc ''
         URL of the UniFi Controller API.
       '';
     };
@@ -19,7 +19,7 @@ in
     unifiInsecure = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If enabled skip the verification of the TLS certificate of the UniFi Controller API.
         Use with caution.
       '';
@@ -28,14 +28,14 @@ in
     unifiUsername = mkOption {
       type = types.str;
       example = "ReadOnlyUser";
-      description = ''
+      description = lib.mdDoc ''
         username for authentication against UniFi Controller API.
       '';
     };
 
     unifiPassword = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Password for authentication against UniFi Controller API.
       '';
     };
@@ -44,7 +44,7 @@ in
       type = types.str;
       default = "5s";
       example = "2m";
-      description = ''
+      description = lib.mdDoc ''
         Timeout including unit for UniFi Controller API requests.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix b/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
new file mode 100644
index 000000000000..5cd1e2c65e90
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/exporters/v2ray.nix b/nixos/modules/services/monitoring/prometheus/exporters/v2ray.nix
new file mode 100644
index 000000000000..a019157c664b
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
index ede6028933a4..a7e5b41dffc6 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -11,35 +11,35 @@ in
     noExit = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Do not exit server on Varnish scrape errors.
       '';
     };
     withGoMetrics = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Export go runtime and http handler metrics.
       '';
     };
     verbose = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable verbose logging.
       '';
     };
     raw = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable raw stdout logging without timestamps.
       '';
     };
     varnishStatPath = mkOption {
       type = types.str;
       default = "varnishstat";
-      description = ''
+      description = lib.mdDoc ''
         Path to varnishstat.
       '';
     };
@@ -47,21 +47,21 @@ in
       type = types.nullOr types.str;
       default = config.services.varnish.stateDir;
       defaultText = lib.literalExpression "config.services.varnish.stateDir";
-      description = ''
+      description = lib.mdDoc ''
         varnishstat -n value.
       '';
     };
     healthPath = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose healthcheck. Disabled unless configured.
       '';
     };
     telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
-      description = ''
+      description = lib.mdDoc ''
         Path under which to expose metrics.
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
index d4aa69629ec8..c98dcd9f64bf 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -11,19 +11,19 @@ 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);
       default = null;
 
-      description = ''
+      description = lib.mdDoc ''
         Path to the Wireguard Config to
-        <link xlink:href="https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/2.0.0#usage">add the peer's name to the stats of a peer</link>.
+        [add the peer's name to the stats of a peer](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/2.0.0#usage).
 
-        Please note that <literal>networking.wg-quick</literal> is required for this feature
-        as <literal>networking.wireguard</literal> uses
-        <citerefentry><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        Please note that `networking.wg-quick` is required for this feature
+        as `networking.wireguard` uses
+        {manpage}`wg(8)`
         to set the peers up.
       '';
     };
@@ -31,18 +31,18 @@ in {
     singleSubnetPerField = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         By default, all allowed IPs and subnets are comma-separated in the
-        <literal>allowed_ips</literal> field. With this option enabled,
-        a single IP and subnet will be listed in fields like <literal>allowed_ip_0</literal>,
-        <literal>allowed_ip_1</literal> and so on.
+        `allowed_ips` field. With this option enabled,
+        a single IP and subnet will be listed in fields like `allowed_ip_0`,
+        `allowed_ip_1` and so on.
       '';
     };
 
     withRemoteIp = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether or not the remote IP of a WireGuard peer should be exposed via prometheus.
       '';
     };
@@ -57,9 +57,9 @@ in {
         ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
           -p ${toString cfg.port} \
           -l ${cfg.listenAddress} \
-          ${optionalString cfg.verbose "-v"} \
-          ${optionalString cfg.singleSubnetPerField "-s"} \
-          ${optionalString cfg.withRemoteIp "-r"} \
+          ${optionalString cfg.verbose "-v true"} \
+          ${optionalString cfg.singleSubnetPerField "-s true"} \
+          ${optionalString cfg.withRemoteIp "-r true"} \
           ${optionalString (cfg.wireguardConfig != null) "-n ${escapeShellArg cfg.wireguardConfig}"}
       '';
       RestrictAddressFamilies = [
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/zfs.nix b/nixos/modules/services/monitoring/prometheus/exporters/zfs.nix
new file mode 100644
index 000000000000..ff12a52d49a9
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
index 01b993762436..f5c114c92752 100644
--- a/nixos/modules/services/monitoring/prometheus/pushgateway.nix
+++ b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -21,13 +21,13 @@ let
 in {
   options = {
     services.prometheus.pushgateway = {
-      enable = mkEnableOption "Prometheus Pushgateway";
+      enable = mkEnableOption (lib.mdDoc "Prometheus Pushgateway");
 
       package = mkOption {
         type = types.package;
         default = pkgs.prometheus-pushgateway;
         defaultText = literalExpression "pkgs.prometheus-pushgateway";
-        description = ''
+        description = lib.mdDoc ''
           Package that should be used for the prometheus pushgateway.
         '';
       };
@@ -35,27 +35,27 @@ in {
       web.listen-address = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Address to listen on for the web interface, API and telemetry.
 
-          <literal>null</literal> will default to <literal>:9091</literal>.
+          `null` will default to `:9091`.
         '';
       };
 
       web.telemetry-path = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path under which to expose metrics.
 
-          <literal>null</literal> will default to <literal>/metrics</literal>.
+          `null` will default to `/metrics`.
         '';
       };
 
       web.external-url = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The URL under which Pushgateway is externally reachable.
         '';
       };
@@ -63,11 +63,11 @@ in {
       web.route-prefix = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Prefix for the internal routes of web endpoints.
 
           Defaults to the path of
-          <option>services.prometheus.pushgateway.web.external-url</option>.
+          {option}`services.prometheus.pushgateway.web.external-url`.
         '';
       };
 
@@ -75,20 +75,20 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "10m";
-        description = ''
+        description = lib.mdDoc ''
           The minimum interval at which to write out the persistence file.
 
-          <literal>null</literal> will default to <literal>5m</literal>.
+          `null` will default to `5m`.
         '';
       };
 
       log.level = mkOption {
         type = types.nullOr (types.enum ["debug" "info" "warn" "error" "fatal"]);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Only log messages with the given severity or above.
 
-          <literal>null</literal> will default to <literal>info</literal>.
+          `null` will default to `info`.
         '';
       };
 
@@ -96,17 +96,17 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "logger:syslog?appname=bob&local=7";
-        description = ''
+        description = lib.mdDoc ''
           Set the log target and format.
 
-          <literal>null</literal> will default to <literal>logger:stderr</literal>.
+          `null` will default to `logger:stderr`.
         '';
       };
 
       extraFlags = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Extra commandline options when launching the Pushgateway.
         '';
       };
@@ -114,26 +114,26 @@ in {
       persistMetrics = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to persist metrics to a file.
 
           When enabled metrics will be saved to a file called
-          <literal>metrics</literal> in the directory
-          <literal>/var/lib/pushgateway</literal>. The directory below
-          <literal>/var/lib</literal> can be set using
-          <option>services.prometheus.pushgateway.stateDir</option>.
+          `metrics` in the directory
+          `/var/lib/pushgateway`. The directory below
+          `/var/lib` can be set using
+          {option}`services.prometheus.pushgateway.stateDir`.
         '';
       };
 
       stateDir = mkOption {
         type = types.str;
         default = "pushgateway";
-        description = ''
-          Directory below <literal>/var/lib</literal> to store metrics.
+        description = lib.mdDoc ''
+          Directory below `/var/lib` to store metrics.
 
           This directory will be created automatically using systemd's
           StateDirectory mechanism when
-          <option>services.prometheus.pushgateway.persistMetrics</option>
+          {option}`services.prometheus.pushgateway.persistMetrics`
           is enabled.
         '';
       };
diff --git a/nixos/modules/services/monitoring/prometheus/sachet.nix b/nixos/modules/services/monitoring/prometheus/sachet.nix
new file mode 100644
index 000000000000..c908d599bd4e
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix b/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
index 980c93c9c478..4545ca37d278 100644
--- a/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
+++ b/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
@@ -15,15 +15,15 @@ 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;
       default = {};
 
-      description = ''
+      description = lib.mdDoc ''
         Configuration for prometheus xmpp-alerts, see
-        <link xlink:href="https://github.com/jelmer/prometheus-xmpp-alerts/blob/master/xmpp-alerts.yml.example"/>
+        <https://github.com/jelmer/prometheus-xmpp-alerts/blob/master/xmpp-alerts.yml.example>
         for supported values.
       '';
     };
diff --git a/nixos/modules/services/monitoring/riemann-dash.nix b/nixos/modules/services/monitoring/riemann-dash.nix
index 16eb83008509..1ca8af14e777 100644
--- a/nixos/modules/services/monitoring/riemann-dash.nix
+++ b/nixos/modules/services/monitoring/riemann-dash.nix
@@ -26,20 +26,20 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the riemann-dash dashboard daemon.
         '';
       };
       config = mkOption {
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Contents added to the end of the riemann-dash configuration file.
         '';
       };
       dataDir = mkOption {
         type = types.str;
         default = "/var/riemann-dash";
-        description = ''
+        description = lib.mdDoc ''
           Location of the riemann-base dir. The dashboard configuration file is
           is stored to this directory. The directory is created automatically on
           service start, and owner is set to the riemanndash user.
diff --git a/nixos/modules/services/monitoring/riemann-tools.nix b/nixos/modules/services/monitoring/riemann-tools.nix
index 86a11694e7b4..28821267b4f3 100644
--- a/nixos/modules/services/monitoring/riemann-tools.nix
+++ b/nixos/modules/services/monitoring/riemann-tools.nix
@@ -23,21 +23,21 @@ in {
       enableHealth = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the riemann-health daemon.
         '';
       };
       riemannHost = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           Address of the host riemann node. Defaults to localhost.
         '';
       };
       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/nixos/modules/services/monitoring/riemann.nix b/nixos/modules/services/monitoring/riemann.nix
index 13d2b1cc0602..7ab8af85ed79 100644
--- a/nixos/modules/services/monitoring/riemann.nix
+++ b/nixos/modules/services/monitoring/riemann.nix
@@ -27,16 +27,11 @@ in {
   options = {
 
     services.riemann = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable the Riemann network monitoring daemon.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc "Riemann network monitoring daemon");
+
       config = mkOption {
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Contents of the Riemann configuration file. For more complicated
           config you should use configFile.
         '';
@@ -44,17 +39,17 @@ in {
       configFiles = mkOption {
         type = with types; listOf path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Extra files containing Riemann configuration. These files will be
           loaded at runtime by Riemann (with Clojure's
-          <literal>load-file</literal> function) at the end of the
+          `load-file` function) at the end of the
           configuration if you use the config option, this is ignored if you
           use configFile.
         '';
       };
       configFile = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           A Riemann config file. Any files in the same directory as this file
           will be added to the classpath by Riemann.
         '';
@@ -62,14 +57,14 @@ in {
       extraClasspathEntries = mkOption {
         type = with types; listOf str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Extra entries added to the Java classpath when running Riemann.
         '';
       };
       extraJavaOpts = mkOption {
         type = with types; listOf str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Extra Java options used when launching Riemann.
         '';
       };
diff --git a/nixos/modules/services/monitoring/scollector.nix b/nixos/modules/services/monitoring/scollector.nix
index 6a6fe110f940..48be309c9599 100644
--- a/nixos/modules/services/monitoring/scollector.nix
+++ b/nixos/modules/services/monitoring/scollector.nix
@@ -35,7 +35,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run scollector.
         '';
       };
@@ -44,7 +44,7 @@ in {
         type = types.package;
         default = pkgs.scollector;
         defaultText = literalExpression "pkgs.scollector";
-        description = ''
+        description = lib.mdDoc ''
           scollector binary to use.
         '';
       };
@@ -52,7 +52,7 @@ in {
       user = mkOption {
         type = types.str;
         default = "scollector";
-        description = ''
+        description = lib.mdDoc ''
           User account under which scollector runs.
         '';
       };
@@ -60,7 +60,7 @@ in {
       group = mkOption {
         type = types.str;
         default = "scollector";
-        description = ''
+        description = lib.mdDoc ''
           Group account under which scollector runs.
         '';
       };
@@ -68,7 +68,7 @@ in {
       bosunHost = mkOption {
         type = types.str;
         default = "localhost:8070";
-        description = ''
+        description = lib.mdDoc ''
           Host and port of the bosun server that will store the collected
           data.
         '';
@@ -78,7 +78,7 @@ in {
         type = with types; attrsOf (listOf path);
         default = {};
         example = literalExpression ''{ "0" = [ "''${postgresStats}/bin/collect-stats" ]; }'';
-        description = ''
+        description = lib.mdDoc ''
           An attribute set mapping the frequency of collection to a list of
           binaries that should be executed at that frequency. You can use "0"
           to run a binary forever.
@@ -89,7 +89,7 @@ in {
         type = with types; listOf str;
         default = [];
         example = [ "-d" ];
-        description = ''
+        description = lib.mdDoc ''
           Extra scollector command line options
         '';
       };
@@ -97,7 +97,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra scollector configuration added to the end of scollector.toml
         '';
       };
diff --git a/nixos/modules/services/monitoring/smartd.nix b/nixos/modules/services/monitoring/smartd.nix
index 6d39cc3e4e6b..1e654cad5dd2 100644
--- a/nixos/modules/services/monitoring/smartd.nix
+++ b/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;
@@ -72,14 +71,14 @@ let
       device = mkOption {
         example = "/dev/sda";
         type = types.str;
-        description = "Location of the device.";
+        description = lib.mdDoc "Location of the device.";
       };
 
       options = mkOption {
         default = "";
         example = "-d sat";
         type = types.separatedString " ";
-        description = "Options that determine how smartd monitors the device.";
+        description = lib.mdDoc "Options that determine how smartd monitors the device.";
       };
 
     };
@@ -95,17 +94,17 @@ 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;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whenever smartd should monitor all devices connected to the
           machine at the time it's being started (the default).
 
           Set to false to monitor the devices listed in
-          <option>services.smartd.devices</option> only.
+          {option}`services.smartd.devices` only.
         '';
       };
 
@@ -113,11 +112,11 @@ in
         default = [];
         type = types.listOf types.str;
         example = ["-A /var/log/smartd/" "--interval=3600"];
-        description = ''
-          Extra command-line options passed to the <literal>smartd</literal>
+        description = lib.mdDoc ''
+          Extra command-line options passed to the `smartd`
           daemon on startup.
 
-          (See <literal>man 8 smartd</literal>.)
+          (See `man 8 smartd`.)
         '';
       };
 
@@ -128,33 +127,33 @@ in
             default = config.services.mail.sendmailSetuidWrapper != null;
             defaultText = literalExpression "config.services.mail.sendmailSetuidWrapper != null";
             type = types.bool;
-            description = "Whenever to send e-mail notifications.";
+            description = lib.mdDoc "Whenever to send e-mail notifications.";
           };
 
           sender = mkOption {
             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.
             '';
           };
 
           recipient = mkOption {
             default = "root";
             type = types.str;
-            description = "Recipient of the notification messages.";
+            description = lib.mdDoc "Recipient of the notification messages.";
           };
 
           mailer = mkOption {
             default = "/run/wrappers/bin/sendmail";
             type = types.path;
-            description = ''
+            description = lib.mdDoc ''
               Sendmail-compatible binary to be used to send the messages.
 
               You should probably enable
-              <option>services.postfix</option> or some other MTA for
+              {option}`services.postfix` or some other MTA for
               this to work.
             '';
           };
@@ -164,7 +163,7 @@ in
           enable = mkOption {
             default = true;
             type = types.bool;
-            description = "Whenever to send wall notifications to all users.";
+            description = lib.mdDoc "Whenever to send wall notifications to all users.";
           };
         };
 
@@ -173,21 +172,21 @@ in
             default = config.services.xserver.enable;
             defaultText = literalExpression "config.services.xserver.enable";
             type = types.bool;
-            description = "Whenever to send X11 xmessage notifications.";
+            description = lib.mdDoc "Whenever to send X11 xmessage notifications.";
           };
 
           display = mkOption {
             default = ":${toString config.services.xserver.display}";
             defaultText = literalExpression ''":''${toString config.services.xserver.display}"'';
             type = types.str;
-            description = "DISPLAY to send X11 notifications to.";
+            description = lib.mdDoc "DISPLAY to send X11 notifications to.";
           };
         };
 
         test = mkOption {
           default = false;
           type = types.bool;
-          description = "Whenever to send a test notification on startup.";
+          description = lib.mdDoc "Whenever to send a test notification on startup.";
         };
 
       };
@@ -197,12 +196,12 @@ in
           default = "-a";
           type = types.separatedString " ";
           example = "-a -o on -s (S/../.././02|L/../../7/04)";
-          description = ''
+          description = lib.mdDoc ''
             Common default options for explicitly monitored (listed in
-            <option>services.smartd.devices</option>) devices.
+            {option}`services.smartd.devices`) devices.
 
             The default value turns on monitoring of all the things (see
-            <literal>man 5 smartd.conf</literal>).
+            `man 5 smartd.conf`).
 
             The example also turns on SMART Automatic Offline Testing on
             startup, and schedules short self-tests daily, and long
@@ -214,8 +213,8 @@ in
           default = cfg.defaults.monitored;
           defaultText = literalExpression "config.${opt.defaults.monitored}";
           type = types.separatedString " ";
-          description = ''
-            Like <option>services.smartd.defaults.monitored</option>, but for the
+          description = lib.mdDoc ''
+            Like {option}`services.smartd.defaults.monitored`, but for the
             autodetected devices.
           '';
         };
@@ -225,7 +224,7 @@ in
         default = [];
         example = [ { device = "/dev/sda"; } { device = "/dev/sdb"; options = "-d sat"; } ];
         type = with types; listOf (submodule smartdDeviceOpts);
-        description = "List of devices to monitor.";
+        description = lib.mdDoc "List of devices to monitor.";
       };
 
     };
diff --git a/nixos/modules/services/monitoring/statsd.nix b/nixos/modules/services/monitoring/statsd.nix
index 30b2916a9928..bbc1c7146a84 100644
--- a/nixos/modules/services/monitoring/statsd.nix
+++ b/nixos/modules/services/monitoring/statsd.nix
@@ -56,34 +56,34 @@ in
 
   options.services.statsd = {
 
-    enable = mkEnableOption "statsd";
+    enable = mkEnableOption (lib.mdDoc "statsd");
 
     listenAddress = mkOption {
-      description = "Address that statsd listens on over UDP";
+      description = lib.mdDoc "Address that statsd listens on over UDP";
       default = "127.0.0.1";
       type = types.str;
     };
 
     port = mkOption {
-      description = "Port that stats listens for messages on over UDP";
+      description = lib.mdDoc "Port that stats listens for messages on over UDP";
       default = 8125;
       type = types.int;
     };
 
     mgmt_address = mkOption {
-      description = "Address to run management TCP interface on";
+      description = lib.mdDoc "Address to run management TCP interface on";
       default = "127.0.0.1";
       type = types.str;
     };
 
     mgmt_port = mkOption {
-      description = "Port to run the management TCP interface on";
+      description = lib.mdDoc "Port to run the management TCP interface on";
       default = 8126;
       type = types.int;
     };
 
     backends = mkOption {
-      description = "List of backends statsd will use for data persistence";
+      description = lib.mdDoc "List of backends statsd will use for data persistence";
       default = [];
       example = [
         "graphite"
@@ -97,19 +97,19 @@ in
     };
 
     graphiteHost = mkOption {
-      description = "Hostname or IP of Graphite server";
+      description = lib.mdDoc "Hostname or IP of Graphite server";
       default = null;
       type = types.nullOr types.str;
     };
 
     graphitePort = mkOption {
-      description = "Port of Graphite server (i.e. carbon-cache).";
+      description = lib.mdDoc "Port of Graphite server (i.e. carbon-cache).";
       default = null;
       type = types.nullOr types.int;
     };
 
     extraConfig = mkOption {
-      description = "Extra configuration options for statsd";
+      description = lib.mdDoc "Extra configuration options for statsd";
       default = "";
       type = types.nullOr types.str;
     };
diff --git a/nixos/modules/services/monitoring/sysstat.nix b/nixos/modules/services/monitoring/sysstat.nix
index ca2cff827232..5468fc3aa454 100644
--- a/nixos/modules/services/monitoring/sysstat.nix
+++ b/nixos/modules/services/monitoring/sysstat.nix
@@ -5,12 +5,12 @@ 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;
         default = "*:00/10";
-        description = ''
+        description = lib.mdDoc ''
           OnCalendar specification for sysstat-collect
         '';
       };
@@ -18,7 +18,7 @@ in {
       collect-args = mkOption {
         type = types.str;
         default = "1 1";
-        description = ''
+        description = lib.mdDoc ''
           Arguments to pass sa1 when collecting statistics
         '';
       };
diff --git a/nixos/modules/services/monitoring/teamviewer.nix b/nixos/modules/services/monitoring/teamviewer.nix
index e2271e571c40..9b1278317943 100644
--- a/nixos/modules/services/monitoring/teamviewer.nix
+++ b/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/nixos/modules/services/monitoring/telegraf.nix b/nixos/modules/services/monitoring/telegraf.nix
index 13aae58d0f37..913e599c189a 100644
--- a/nixos/modules/services/monitoring/telegraf.nix
+++ b/nixos/modules/services/monitoring/telegraf.nix
@@ -11,12 +11,12 @@ in {
   ###### interface
   options = {
     services.telegraf = {
-      enable = mkEnableOption "telegraf server";
+      enable = mkEnableOption (lib.mdDoc "telegraf server");
 
       package = mkOption {
         default = pkgs.telegraf;
         defaultText = literalExpression "pkgs.telegraf";
-        description = "Which telegraf derivation to use";
+        description = lib.mdDoc "Which telegraf derivation to use";
         type = types.package;
       };
 
@@ -24,17 +24,17 @@ in {
         type = types.listOf types.path;
         default = [];
         example = [ "/run/keys/telegraf.env" ];
-        description = ''
+        description = lib.mdDoc ''
           File to load as environment file. Environment variables from this file
           will be interpolated into the config file using envsubst with this
-          syntax: <literal>$ENVIRONMENT</literal> or <literal>''${VARIABLE}</literal>.
+          syntax: `$ENVIRONMENT` or `''${VARIABLE}`.
           This is useful to avoid putting secrets into the nix store.
         '';
       };
 
       extraConfig = mkOption {
         default = {};
-        description = "Extra configuration options for telegraf";
+        description = lib.mdDoc "Extra configuration options for telegraf";
         type = settingsFormat.type;
         example = {
           outputs.influxdb = {
diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
index 9e93d8dbb0ef..e6d8afc66624 100644
--- a/nixos/modules/services/monitoring/thanos.nix
+++ b/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,14 +83,14 @@ 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 = ''
-      Arguments to the <literal>thanos ${cmd}</literal> command.
+    description = lib.mdDoc ''
+      Arguments to the `thanos ${cmd}` command.
 
       Defaults to a list of arguments formed by converting the structured
-      options of <option>services.thanos.${cmd}</option> to a list of arguments.
+      options of {option}`services.thanos.${cmd}` to a list of arguments.
 
       Overriding this option will cause none of the structured options to have
       any effect. So only set this if you know what you're doing!
@@ -127,10 +127,10 @@ let
             if config.services.thanos.<cmd>.tracing.config == null then null
             else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config);
           '';
-          description = ''
+          description = lib.mdDoc ''
             Path to YAML file that contains tracing configuration.
 
-            See format details: <link xlink:href="https://thanos.io/tracing.md/#configuration"/>
+            See format details: <https://thanos.io/tracing.md/#configuration>
           '';
         };
       };
@@ -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.
       '';
@@ -192,10 +192,10 @@ let
             if config.services.thanos.<cmd>.objstore.config == null then null
             else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config);
           '';
-          description = ''
+          description = lib.mdDoc ''
             Path to YAML file that contains object store configuration.
 
-            See format details: <link xlink:href="https://thanos.io/storage.md/#configuration"/>
+            See format details: <https://thanos.io/storage.md/#configuration>
           '';
         };
       };
@@ -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>
           '';
         };
     };
@@ -231,7 +231,7 @@ let
           type = types.str;
           default = "/var/lib/${config.services.prometheus.stateDir}/data";
           defaultText = literalExpression ''"/var/lib/''${config.services.prometheus.stateDir}/data"'';
-          description = ''
+          description = lib.mdDoc ''
             Data directory of TSDB.
           '';
         };
@@ -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
       '';
     };
 
@@ -660,53 +660,53 @@ in {
       type = types.package;
       default = pkgs.thanos;
       defaultText = literalExpression "pkgs.thanos";
-      description = ''
+      description = lib.mdDoc ''
         The thanos package that should be used.
       '';
     };
 
     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/nixos/modules/services/monitoring/tremor-rs.nix b/nixos/modules/services/monitoring/tremor-rs.nix
new file mode 100644
index 000000000000..213e8a474868
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/tuptime.nix b/nixos/modules/services/monitoring/tuptime.nix
index de80282559ae..d97e408bce31 100644
--- a/nixos/modules/services/monitoring/tuptime.nix
+++ b/nixos/modules/services/monitoring/tuptime.nix
@@ -10,19 +10,19 @@ in {
 
   options.services.tuptime = {
 
-    enable = mkEnableOption "the total uptime service";
+    enable = mkEnableOption (lib.mdDoc "the total uptime service");
 
     timer = {
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to regularly log uptime to detect bad shutdowns.";
+        description = lib.mdDoc "Whether to regularly log uptime to detect bad shutdowns.";
       };
 
       period = mkOption {
         type = types.str;
         default = "*:0/5";
-        description = "systemd calendar event";
+        description = lib.mdDoc "systemd calendar event";
       };
     };
   };
@@ -45,7 +45,7 @@ in {
       services = {
 
         tuptime = {
-          description = "the total uptime service";
+          description = "The total uptime service";
           documentation = [ "man:tuptime(1)" ];
           after = [ "time-sync.target" ];
           wantedBy = [ "multi-user.target" ];
@@ -59,10 +59,9 @@ in {
           };
         };
 
-        tuptime-oneshot = mkIf cfg.timer.enable {
-          description = "the tuptime scheduled execution unit";
+        tuptime-sync = mkIf cfg.timer.enable {
+          description = "Tuptime scheduled sync service";
           serviceConfig = {
-            StateDirectory = "tuptime";
             Type = "oneshot";
             User = "_tuptime";
             ExecStart = "${pkgs.tuptime}/bin/tuptime -x";
@@ -70,8 +69,8 @@ in {
         };
       };
 
-      timers.tuptime = mkIf cfg.timer.enable {
-        description = "the tuptime scheduled execution timer";
+      timers.tuptime-sync = mkIf cfg.timer.enable {
+        description = "Tuptime scheduled sync timer";
         # this timer should be started if the service is started
         # even if the timer was previously stopped
         wantedBy = [ "tuptime.service" "timers.target" ];
@@ -80,7 +79,7 @@ in {
         timerConfig = {
           OnBootSec = "1min";
           OnCalendar = cfg.timer.period;
-          Unit = "tuptime-oneshot.service";
+          Unit = "tuptime-sync.service";
         };
       };
     };
diff --git a/nixos/modules/services/monitoring/unifi-poller.nix b/nixos/modules/services/monitoring/unpoller.nix
index cca4a0e72071..f0ced5513d64 100644
--- a/nixos/modules/services/monitoring/unifi-poller.nix
+++ b/nixos/modules/services/monitoring/unpoller.nix
@@ -3,21 +3,25 @@
 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 {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Turns on line numbers, microsecond logging, and a per-device log.
           This may be noisy if you have a lot of devices. It adds one line per device.
         '';
@@ -25,14 +29,14 @@ in {
       quiet = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Turns off per-interval logs. Only startup and error logs will be emitted.
         '';
       };
       plugins = mkOption {
         type = with types; listOf str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Load additional plugins.
         '';
       };
@@ -42,21 +46,21 @@ in {
       disable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to disable the prometheus ouput plugin.
         '';
       };
       http_listen = mkOption {
         type = types.str;
         default = "[::]:9130";
-        description = ''
+        description = lib.mdDoc ''
           Bind the prometheus exporter to this IP or hostname.
         '';
       };
       report_errors = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to report errors.
         '';
       };
@@ -66,29 +70,29 @@ in {
       disable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to disable the influxdb ouput plugin.
         '';
       };
       url = mkOption {
         type = types.str;
         default = "http://127.0.0.1:8086";
-        description = ''
+        description = lib.mdDoc ''
           URL of the influxdb host.
         '';
       };
       user = mkOption {
         type = types.str;
         default = "unifipoller";
-        description = ''
+        description = lib.mdDoc ''
           Username for the influxdb.
         '';
       };
       pass = mkOption {
         type = types.path;
-        default = pkgs.writeText "unifi-poller-influxdb-default.password" "unifipoller";
-        defaultText = literalExpression "unifi-poller-influxdb-default.password";
-        description = ''
+        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.
         '';
@@ -97,21 +101,21 @@ in {
       db = mkOption {
         type = types.str;
         default = "unifi";
-        description = ''
+        description = lib.mdDoc ''
           Database name. Database should exist.
         '';
       };
       verify_ssl = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Verify the influxdb's certificate.
         '';
       };
       interval = mkOption {
         type = types.str;
         default = "30s";
-        description = ''
+        description = lib.mdDoc ''
           Setting this lower than the Unifi controller's refresh
           interval may lead to zeroes in your database.
         '';
@@ -122,22 +126,22 @@ in {
       url = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           URL of the Loki host.
         '';
       };
       user = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Username for Loki.
         '';
       };
       pass = mkOption {
         type = types.path;
-        default = pkgs.writeText "unifi-poller-loki-default.password" "";
-        defaultText = "unifi-poller-influxdb-default.password";
-        description = ''
+        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.
         '';
@@ -146,28 +150,28 @@ in {
       verify_ssl = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Verify Loki's certificate.
         '';
       };
       tenant_id = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Tenant ID to use in Loki.
         '';
       };
       interval = mkOption {
         type = types.str;
         default = "2m";
-        description = ''
+        description = lib.mdDoc ''
           How often the events are polled and pushed to Loki.
         '';
       };
       timeout = mkOption {
         type = types.str;
         default = "10s";
-        description = ''
+        description = lib.mdDoc ''
           Should be increased in case of timeout errors.
         '';
       };
@@ -178,15 +182,15 @@ in {
         user = mkOption {
           type = types.str;
           default = "unifi";
-          description = ''
+          description = lib.mdDoc ''
             Unifi service user name.
           '';
         };
         pass = mkOption {
           type = types.path;
-          default = pkgs.writeText "unifi-poller-unifi-default.password" "unifi";
-          defaultText = literalExpression "unifi-poller-unifi-default.password";
-          description = ''
+          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.
           '';
@@ -195,14 +199,14 @@ in {
         url = mkOption {
           type = types.str;
           default = "https://unifi:8443";
-          description = ''
+          description = lib.mdDoc ''
             URL of the Unifi controller.
           '';
         };
         sites = mkOption {
           type = with types; either (enum [ "default" "all" ]) (listOf str);
           default = "all";
-          description = ''
+          description = lib.mdDoc ''
             List of site names for which statistics should be exported.
             Or the string "default" for the default site or the string "all" for all sites.
           '';
@@ -211,35 +215,35 @@ in {
         save_ids = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Collect and save data from the intrusion detection system to influxdb and Loki.
           '';
         };
         save_events = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Collect and save data from UniFi events to influxdb and Loki.
           '';
         };
         save_alarms = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Collect and save data from UniFi alarms to influxdb and Loki.
           '';
         };
         save_anomalies = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Collect and save data from UniFi anomalies to influxdb and Loki.
           '';
         };
         save_dpi = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Collect and save data from deep packet inspection.
             Adds around 150 data points and impacts performance.
           '';
@@ -247,14 +251,14 @@ in {
         save_sites = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Collect and save site data.
           '';
         };
         hash_pii = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Hash, with md5, client names and MAC addresses. This attempts
             to protect personally identifiable information.
           '';
@@ -262,7 +266,7 @@ in {
         verify_ssl = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Verify the Unifi controller's certificate.
           '';
         };
@@ -272,7 +276,7 @@ in {
       dynamic = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Let prometheus select which controller to poll when scraping.
           Use with default credentials. See unifi-poller wiki for more.
         '';
@@ -283,7 +287,7 @@ in {
       controllers = mkOption {
         type = with types; listOf (submodule { options = controllerOptions; });
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of Unifi controllers to poll. Use defaults if empty.
         '';
         apply = map (flip removeAttrs [ "_module" ]);
@@ -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/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix
index ae5097c54424..bb11b6a1c1d0 100644
--- a/nixos/modules/services/monitoring/ups.nix
+++ b/nixos/modules/services/monitoring/ups.nix
@@ -12,11 +12,11 @@ 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;
-        description = ''
+        description = lib.mdDoc ''
           Specify the program to run to talk to this UPS.  apcsmart,
           bestups, and sec are some examples.
         '';
@@ -24,7 +24,7 @@ let
 
       port = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The serial port to which your UPS is connected.  /dev/ttyS0 is
           usually the first port on Linux boxes, for example.
         '';
@@ -33,7 +33,7 @@ let
       shutdownOrder = mkOption {
         default = 0;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           When you have multiple UPSes on your system, you usually need to
           turn them off in a certain order.  upsdrvctl shuts down all the
           0s, then the 1s, 2s, and so on.  To exclude a UPS from the
@@ -44,7 +44,7 @@ let
       maxStartDelay = mkOption {
         default = null;
         type = types.uniq (types.nullOr types.int);
-        description = ''
+        description = lib.mdDoc ''
           This can be set as a global variable above your first UPS
           definition and it can also be set in a UPS section.  This value
           controls how long upsdrvctl will wait for the driver to finish
@@ -56,7 +56,7 @@ let
       description = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Description of the UPS.
         '';
       };
@@ -64,7 +64,7 @@ let
       directives = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of configuration directives for this UPS.
         '';
       };
@@ -72,7 +72,7 @@ let
       summary = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Lines which would be added inside ups.conf for handling this UPS.
         '';
       };
@@ -106,7 +106,7 @@ in
       enable = mkOption {
         default = false;
         type = with types; bool;
-        description = ''
+        description = lib.mdDoc ''
           Enables support for Power Devices, such as Uninterruptible Power
           Supplies, Power Distribution Units and Solar Controllers.
         '';
@@ -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.
 
@@ -143,7 +143,7 @@ in
       schedulerRules = mkOption {
         example = "/etc/nixos/upssched.conf";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           File which contains the rules to handle UPS events.
         '';
       };
@@ -152,7 +152,7 @@ in
       maxStartDelay = mkOption {
         default = 45;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           This can be set as a global variable above your first UPS
           definition and it can also be set in a UPS section.  This value
           controls how long upsdrvctl will wait for the driver to finish
@@ -164,7 +164,7 @@ in
       ups = mkOption {
         default = {};
         # see nut/etc/ups.conf.sample
-        description = ''
+        description = lib.mdDoc ''
           This is where you configure all the UPSes that this system will be
           monitoring directly.  These are usually attached to serial ports,
           but USB devices are also supported.
@@ -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/nixos/modules/services/monitoring/uptime-kuma.nix b/nixos/modules/services/monitoring/uptime-kuma.nix
new file mode 100644
index 000000000000..b6dc993e6a05
--- /dev/null
+++ b/nixos/modules/services/monitoring/uptime-kuma.nix
@@ -0,0 +1,75 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.uptime-kuma;
+in
+{
+
+  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.";
+      };
+
+      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";
+    };
+
+    systemd.services.uptime-kuma = {
+      description = "Uptime Kuma";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = cfg.settings;
+      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/nixos/modules/services/monitoring/uptime.nix b/nixos/modules/services/monitoring/uptime.nix
index 79b86be6cc71..7bf9e593c95e 100644
--- a/nixos/modules/services/monitoring/uptime.nix
+++ b/nixos/modules/services/monitoring/uptime.nix
@@ -26,7 +26,7 @@ let
 in {
   options.services.uptime = {
     configFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The uptime configuration file
 
         If mongodb: server != localhost, please set usesRemoteMongo = true
@@ -44,22 +44,22 @@ in {
     };
 
     usesRemoteMongo = mkOption {
-      description = "Whether the configuration file specifies a remote mongo instance";
+      description = lib.mdDoc "Whether the configuration file specifies a remote mongo instance";
 
       default = false;
 
       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}";
     };
 
     nodeEnv = mkOption {
-      description = "The node environment to run in (development, production, etc.)";
+      description = lib.mdDoc "The node environment to run in (development, production, etc.)";
 
       type = types.str;
 
diff --git a/nixos/modules/services/monitoring/vmagent.nix b/nixos/modules/services/monitoring/vmagent.nix
new file mode 100644
index 000000000000..c793bb073199
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/vnstat.nix b/nixos/modules/services/monitoring/vnstat.nix
index 5e19c399568d..a498962ae57e 100644
--- a/nixos/modules/services/monitoring/vnstat.nix
+++ b/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/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index c48b973f1ef7..b497ecbcdb6c 100644
--- a/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -29,13 +29,13 @@ in
   options = {
 
     services.zabbixAgent = {
-      enable = mkEnableOption "the Zabbix Agent";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix Agent");
 
       package = mkOption {
         type = types.package;
         default = pkgs.zabbix.agent;
         defaultText = literalExpression "pkgs.zabbix.agent";
-        description = "The Zabbix package to use.";
+        description = lib.mdDoc "The Zabbix package to use.";
       };
 
       extraPackages = mkOption {
@@ -43,15 +43,15 @@ 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.
         '';
       };
 
       modules = mkOption {
         type = types.attrsOf types.package;
-        description = "A set of modules to load.";
+        description = lib.mdDoc "A set of modules to load.";
         default = {};
         example = literalExpression ''
           {
@@ -71,7 +71,7 @@ in
 
       server = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The IP address or hostname of the Zabbix server to connect to.
         '';
       };
@@ -80,7 +80,7 @@ in
         ip = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = ''
+          description = lib.mdDoc ''
             List of comma delimited IP addresses that the agent should listen on.
           '';
         };
@@ -88,7 +88,7 @@ in
         port = mkOption {
           type = types.port;
           default = 10050;
-          description = ''
+          description = lib.mdDoc ''
             Agent will listen on this port for connections from the server.
           '';
         };
@@ -97,7 +97,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the Zabbix Agent.
         '';
       };
@@ -105,9 +105,9 @@ in
       settings = mkOption {
         type = with types; attrsOf (oneOf [ int str (listOf str) ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Zabbix Agent configuration. Refer to
-          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_agentd"/>
+          <https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_agentd>
           for details on supported values.
         '';
         example = {
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
index 0ebd7bcff834..85da416ba6c3 100644
--- a/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -38,11 +38,11 @@ in
   options = {
 
     services.zabbixProxy = {
-      enable = mkEnableOption "the Zabbix Proxy";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix Proxy");
 
       server = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The IP address or hostname of the Zabbix server to connect to.
           '';
         };
@@ -54,22 +54,22 @@ in
           else if cfg.database.type == "pgsql" then pkgs.zabbix.proxy-pgsql
           else pkgs.zabbix.proxy-sqlite;
         defaultText = literalExpression "pkgs.zabbix.proxy-pgsql";
-        description = "The Zabbix package to use.";
+        description = lib.mdDoc "The Zabbix package to use.";
       };
 
       extraPackages = mkOption {
         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.
         '';
       };
 
       modules = mkOption {
         type = types.attrsOf types.package;
-        description = "A set of modules to load.";
+        description = lib.mdDoc "A set of modules to load.";
         default = {};
         example = literalExpression ''
           {
@@ -92,46 +92,46 @@ in
           type = types.enum [ "mysql" "pgsql" "sqlite" ];
           example = "mysql";
           default = "pgsql";
-          description = "Database engine to use.";
+          description = lib.mdDoc "Database engine to use.";
         };
 
         host = mkOption {
           type = types.str;
           default = "localhost";
-          description = "Database host address.";
+          description = lib.mdDoc "Database host address.";
         };
 
         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"
             then config.${options.services.mysql.port}
             else config.${options.services.postgresql.port}
           '';
-          description = "Database host port.";
+          description = lib.mdDoc "Database host port.";
         };
 
         name = mkOption {
           type = types.str;
           default = if cfg.database.type == "sqlite" then "${stateDir}/zabbix.db" else "zabbix";
           defaultText = literalExpression "zabbix";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = mkOption {
           type = types.str;
           default = "zabbix";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/zabbix-dbpassword";
-          description = ''
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
 
@@ -139,13 +139,13 @@ in
           type = types.nullOr types.path;
           default = null;
           example = "/run/postgresql";
-          description = "Path to the unix socket file to use for authentication.";
+          description = lib.mdDoc "Path to the unix socket file to use for authentication.";
         };
 
         createLocally = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether to create a local database automatically.";
+          description = lib.mdDoc "Whether to create a local database automatically.";
         };
       };
 
@@ -153,7 +153,7 @@ in
         ip = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = ''
+          description = lib.mdDoc ''
             List of comma delimited IP addresses that the trapper should listen on.
             Trapper will listen on all network interfaces if this parameter is missing.
           '';
@@ -162,7 +162,7 @@ in
         port = mkOption {
           type = types.port;
           default = 10051;
-          description = ''
+          description = lib.mdDoc ''
             Listen port for trapper.
           '';
         };
@@ -171,7 +171,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the Zabbix Proxy.
         '';
       };
@@ -179,9 +179,9 @@ in
       settings = mkOption {
         type = with types; attrsOf (oneOf [ int str (listOf str) ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Zabbix Proxy configuration. Refer to
-          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy"/>
+          <https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy>
           for details on supported values.
         '';
         example = {
diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index 9f960517a81b..2b50280e3969 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -40,28 +40,28 @@ in
   options = {
 
     services.zabbixServer = {
-      enable = mkEnableOption "the Zabbix Server";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix Server");
 
       package = mkOption {
         type = types.package;
         default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql;
         defaultText = literalExpression "pkgs.zabbix.server-pgsql";
-        description = "The Zabbix package to use.";
+        description = lib.mdDoc "The Zabbix package to use.";
       };
 
       extraPackages = mkOption {
         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.
         '';
       };
 
       modules = mkOption {
         type = types.attrsOf types.package;
-        description = "A set of modules to load.";
+        description = lib.mdDoc "A set of modules to load.";
         default = {};
         example = literalExpression ''
           {
@@ -84,45 +84,45 @@ in
           type = types.enum [ "mysql" "pgsql" ];
           example = "mysql";
           default = "pgsql";
-          description = "Database engine to use.";
+          description = lib.mdDoc "Database engine to use.";
         };
 
         host = mkOption {
           type = types.str;
           default = "localhost";
-          description = "Database host address.";
+          description = lib.mdDoc "Database host address.";
         };
 
         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"
             then config.${options.services.mysql.port}
             else config.${options.services.postgresql.port}
           '';
-          description = "Database host port.";
+          description = lib.mdDoc "Database host port.";
         };
 
         name = mkOption {
           type = types.str;
           default = "zabbix";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = mkOption {
           type = types.str;
           default = "zabbix";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/zabbix-dbpassword";
-          description = ''
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
 
@@ -130,13 +130,13 @@ in
           type = types.nullOr types.path;
           default = null;
           example = "/run/postgresql";
-          description = "Path to the unix socket file to use for authentication.";
+          description = lib.mdDoc "Path to the unix socket file to use for authentication.";
         };
 
         createLocally = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether to create a local database automatically.";
+          description = lib.mdDoc "Whether to create a local database automatically.";
         };
       };
 
@@ -144,7 +144,7 @@ in
         ip = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = ''
+          description = lib.mdDoc ''
             List of comma delimited IP addresses that the trapper should listen on.
             Trapper will listen on all network interfaces if this parameter is missing.
           '';
@@ -153,7 +153,7 @@ in
         port = mkOption {
           type = types.port;
           default = 10051;
-          description = ''
+          description = lib.mdDoc ''
             Listen port for trapper.
           '';
         };
@@ -162,7 +162,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the Zabbix Server.
         '';
       };
@@ -170,9 +170,9 @@ in
       settings = mkOption {
         type = with types; attrsOf (oneOf [ int str (listOf str) ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Zabbix Server configuration. Refer to
-          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server"/>
+          <https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server>
           for details on supported values.
         '';
         example = {
diff --git a/nixos/modules/services/network-filesystems/cachefilesd.nix b/nixos/modules/services/network-filesystems/cachefilesd.nix
index 229c9665419f..da5a79a062c7 100644
--- a/nixos/modules/services/network-filesystems/cachefilesd.nix
+++ b/nixos/modules/services/network-filesystems/cachefilesd.nix
@@ -20,20 +20,20 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable cachefilesd network filesystems caching daemon.";
+        description = lib.mdDoc "Whether to enable cachefilesd network filesystems caching daemon.";
       };
 
       cacheDir = mkOption {
         type = types.str;
         default = "/var/cache/fscache";
-        description = "Directory to contain filesystem cache.";
+        description = lib.mdDoc "Directory to contain filesystem cache.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
         example = "brun 10%";
-        description = "Additional configuration file entries. See cachefilesd.conf(5) for more information.";
+        description = lib.mdDoc "Additional configuration file entries. See cachefilesd.conf(5) for more information.";
       };
 
     };
diff --git a/nixos/modules/services/network-filesystems/ceph.nix b/nixos/modules/services/network-filesystems/ceph.nix
index 7a1444decafa..22d58f29cb81 100644
--- a/nixos/modules/services/network-filesystems/ceph.nix
+++ b/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 {
@@ -80,7 +80,7 @@ in
         example = ''
           433a2193-4f8a-47a0-95d2-209d7ca2cca5
         '';
-        description = ''
+        description = lib.mdDoc ''
           Filesystem ID, a generated uuid, its must be generated and set before
           attempting to start a cluster
         '';
@@ -89,7 +89,7 @@ in
       clusterName = mkOption {
         type = types.str;
         default = "ceph";
-        description = ''
+        description = lib.mdDoc ''
           Name of cluster
         '';
       };
@@ -98,7 +98,7 @@ in
         type = types.path;
         default = "${pkgs.ceph.lib}/lib/ceph/mgr";
         defaultText = literalExpression ''"''${pkgs.ceph.lib}/lib/ceph/mgr"'';
-        description = ''
+        description = lib.mdDoc ''
           Path at which to find ceph-mgr modules.
         '';
       };
@@ -109,7 +109,7 @@ in
         example = ''
           node0, node1, node2
         '';
-        description = ''
+        description = lib.mdDoc ''
           List of hosts that will be used as monitors at startup.
         '';
       };
@@ -120,7 +120,7 @@ in
         example = ''
           10.10.0.1, 10.10.0.2, 10.10.0.3
         '';
-        description = ''
+        description = lib.mdDoc ''
           List of hostname shortnames/IP addresses of the initial monitors.
         '';
       };
@@ -128,7 +128,7 @@ in
       maxOpenFiles = mkOption {
         type = types.int;
         default = 131072;
-        description = ''
+        description = lib.mdDoc ''
           Max open files for each OSD daemon.
         '';
       };
@@ -136,7 +136,7 @@ in
       authClusterRequired = mkOption {
         type = types.enum [ "cephx" "none" ];
         default = "cephx";
-        description = ''
+        description = lib.mdDoc ''
           Enables requiring daemons to authenticate with eachother in the cluster.
         '';
       };
@@ -144,7 +144,7 @@ in
       authServiceRequired = mkOption {
         type = types.enum [ "cephx" "none" ];
         default = "cephx";
-        description = ''
+        description = lib.mdDoc ''
           Enables requiring clients to authenticate with the cluster to access services in the cluster (e.g. radosgw, mds or osd).
         '';
       };
@@ -152,7 +152,7 @@ in
       authClientRequired = mkOption {
         type = types.enum [ "cephx" "none" ];
         default = "cephx";
-        description = ''
+        description = lib.mdDoc ''
           Enables requiring the cluster to authenticate itself to the client.
         '';
       };
@@ -163,7 +163,7 @@ in
         example = ''
           10.20.0.0/24, 192.168.1.0/24
         '';
-        description = ''
+        description = lib.mdDoc ''
           A comma-separated list of subnets that will be used as public networks in the cluster.
         '';
       };
@@ -174,7 +174,7 @@ in
         example = ''
           10.10.0.0/24, 192.168.0.0/24
         '';
-        description = ''
+        description = lib.mdDoc ''
           A comma-separated list of subnets that will be used as cluster networks in the cluster.
         '';
       };
@@ -183,7 +183,7 @@ in
         type = with types; nullOr path;
         default = "${pkgs.mailcap}/etc/mime.types";
         defaultText = literalExpression ''"''${pkgs.mailcap}/etc/mime.types"'';
-        description = ''
+        description = lib.mdDoc ''
           Path to mime types used by radosgw.
         '';
       };
@@ -195,18 +195,18 @@ in
       example = {
         "ms bind ipv6" = "true";
       };
-      description = ''
+      description = lib.mdDoc ''
         Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
       '';
     };
 
     mgr = {
-      enable = mkEnableOption "Ceph MGR daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph MGR daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
         example = [ "name1" "name2" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of names for manager daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
         '';
@@ -214,19 +214,19 @@ in
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add to the global section for manager daemons.
         '';
       };
     };
 
     mon = {
-      enable = mkEnableOption "Ceph MON daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph MON daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
         example = [ "name1" "name2" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of monitor daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in mon.name1
         '';
@@ -234,19 +234,19 @@ in
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add to the monitor section.
         '';
       };
     };
 
     osd = {
-      enable = mkEnableOption "Ceph OSD daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph OSD daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
         example = [ "name1" "name2" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of OSD daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in osd.name1
         '';
@@ -262,19 +262,19 @@ in
           "osd pool default pgp num" = "200";
           "osd crush chooseleaf type" = "1";
         };
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add to the OSD section.
         '';
       };
     };
 
     mds = {
-      enable = mkEnableOption "Ceph MDS daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph MDS daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
         example = [ "name1" "name2" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of metadata service daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in mds.name1
         '';
@@ -282,19 +282,19 @@ in
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add to the MDS section.
         '';
       };
     };
 
     rgw = {
-      enable = mkEnableOption "Ceph RadosGW daemon";
+      enable = mkEnableOption (lib.mdDoc "Ceph RadosGW daemon");
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
         example = [ "name1" "name2" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of rados gateway daemons that should have a service created. The names correspond
           to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
           aren't daemons to cluster in the sense that OSD, MGR or MON daemons are. They are simply
@@ -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 = {};
@@ -315,7 +315,7 @@ in
             "client.radosgw.node0" = { "some config option" = "true"; };
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration to add to the client section. Configuration for rados gateways
           would be added here, with their own sections, see example.
         '';
diff --git a/nixos/modules/services/network-filesystems/davfs2.nix b/nixos/modules/services/network-filesystems/davfs2.nix
index 8cf314fe63a5..8024cfba08be 100644
--- a/nixos/modules/services/network-filesystems/davfs2.nix
+++ b/nixos/modules/services/network-filesystems/davfs2.nix
@@ -15,7 +15,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable davfs2.
       '';
     };
@@ -23,7 +23,7 @@ in
     davUser = mkOption {
       type = types.str;
       default = "davfs2";
-      description = ''
+      description = lib.mdDoc ''
         When invoked by root the mount.davfs daemon will run as this user.
         Value must be given as name, not as numerical id.
       '';
@@ -32,7 +32,7 @@ in
     davGroup = mkOption {
       type = types.str;
       default = "davfs2";
-      description = ''
+      description = lib.mdDoc ''
         The group of the running mount.davfs daemon. Ordinary users must be
         member of this group in order to mount a davfs2 file system. Value must
         be given as name, not as numerical id.
@@ -47,7 +47,7 @@ in
         proxy foo.bar:8080
         use_locks 0
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra lines appended to the configuration of davfs2.
       ''  ;
     };
diff --git a/nixos/modules/services/network-filesystems/diod.nix b/nixos/modules/services/network-filesystems/diod.nix
index 063bae6ddb1d..541b4ffd6b46 100644
--- a/nixos/modules/services/network-filesystems/diod.nix
+++ b/nixos/modules/services/network-filesystems/diod.nix
@@ -26,13 +26,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the diod 9P file server.";
+        description = lib.mdDoc "Whether to enable the diod 9P file server.";
       };
 
       listen = mkOption {
         type = types.listOf types.str;
         default = [ "0.0.0.0:564" ];
-        description = ''
+        description = lib.mdDoc ''
           [ "IP:PORT" [,"IP:PORT",...] ]
           List the interfaces and ports that diod should listen on.
         '';
@@ -41,7 +41,7 @@ in
       exports = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List the file systems that clients will be allowed to mount. All paths should
           be fully qualified. The exports table can include two types of element:
           a string element (as above),
@@ -57,7 +57,7 @@ in
       exportall = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Export all file systems listed in /proc/mounts. If new file systems are mounted
           after diod has started, they will become immediately mountable. If there is a
           duplicate entry for a file system in the exports list, any options listed in
@@ -68,7 +68,7 @@ in
       exportopts = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Establish a default set of export options. These are overridden, not appended
           to, by opts attributes in an "exports" entry.
         '';
@@ -77,7 +77,7 @@ in
       nwthreads = mkOption {
         type = types.int;
         default = 16;
-        description = ''
+        description = lib.mdDoc ''
           Sets the (fixed) number of worker threads created to handle 9P
           requests for a unique aname.
         '';
@@ -86,7 +86,7 @@ in
       authRequired = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Allow clients to connect without authentication, i.e. without a valid MUNGE credential.
         '';
       };
@@ -94,7 +94,7 @@ in
       userdb = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This option disables password/group lookups. It allows any uid to attach and
           assumes gid=uid, and supplementary groups contain only the primary gid.
         '';
@@ -103,7 +103,7 @@ in
       allsquash = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Remap all users to "nobody". The attaching user need not be present in the
           password file.
         '';
@@ -112,7 +112,7 @@ in
       squashuser = mkOption {
         type = types.str;
         default = "nobody";
-        description = ''
+        description = lib.mdDoc ''
           Change the squash user. The squash user must be present in the password file.
         '';
       };
@@ -120,7 +120,7 @@ in
       logdest = mkOption {
         type = types.str;
         default = "syslog:daemon:err";
-        description = ''
+        description = lib.mdDoc ''
           Set the destination for logging.
           The value has the form of "syslog:facility:level" or "filename".
         '';
@@ -130,7 +130,7 @@ in
       statfsPassthru = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This option configures statfs to return the host file system's type
           rather than V9FS_MAGIC.
         '';
@@ -139,7 +139,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra configuration options for diod.conf.";
+        description = lib.mdDoc "Extra configuration options for diod.conf.";
       };
     };
   };
diff --git a/nixos/modules/services/network-filesystems/drbd.nix b/nixos/modules/services/network-filesystems/drbd.nix
index c730e0b34e90..e74ed391d48e 100644
--- a/nixos/modules/services/network-filesystems/drbd.nix
+++ b/nixos/modules/services/network-filesystems/drbd.nix
@@ -15,7 +15,7 @@ let cfg = config.services.drbd; in
     services.drbd.enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable support for DRBD, the Distributed Replicated
         Block Device.
       '';
@@ -24,8 +24,8 @@ let cfg = config.services.drbd; in
     services.drbd.config = mkOption {
       default = "";
       type = types.lines;
-      description = ''
-        Contents of the <filename>drbd.conf</filename> configuration file.
+      description = lib.mdDoc ''
+        Contents of the {file}`drbd.conf` configuration file.
       '';
     };
 
diff --git a/nixos/modules/services/network-filesystems/glusterfs.nix b/nixos/modules/services/network-filesystems/glusterfs.nix
index 38be098de5d9..5c3e197b687d 100644
--- a/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -33,17 +33,17 @@ 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"];
-        description = "Log level used by the GlusterFS daemon";
+        description = lib.mdDoc "Log level used by the GlusterFS daemon";
         default = "INFO";
       };
 
       useRpcbind = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable use of rpcbind. This is required for Gluster's NFS functionality.
 
           You may want to turn it off to reduce the attack surface for DDoS reflection attacks.
@@ -56,13 +56,13 @@ in
 
       enableGlustereventsd = mkOption {
         type = types.bool;
-        description = "Whether to enable the GlusterFS Events Daemon";
+        description = lib.mdDoc "Whether to enable the GlusterFS Events Daemon";
         default = true;
       };
 
       killMode = mkOption {
         type = types.enum ["control-group" "process" "mixed" "none"];
-        description = ''
+        description = lib.mdDoc ''
           The systemd KillMode to use for glusterd.
 
           glusterd spawns other daemons like gsyncd.
@@ -79,7 +79,7 @@ in
 
       stopKillTimeout = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The systemd TimeoutStopSec to use.
 
           After this time after having been asked to shut down, glusterd
@@ -94,17 +94,17 @@ in
 
       extraFlags = mkOption {
         type = types.listOf types.str;
-        description = "Extra flags passed to the GlusterFS daemon";
+        description = lib.mdDoc "Extra flags passed to the GlusterFS daemon";
         default = [];
       };
 
       tlsSettings = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Make the server communicate via TLS.
           This means it will only connect to other gluster
           servers having certificates signed by the same CA.
 
-          Enabling this will create a file <filename>/var/lib/glusterd/secure-access</filename>.
+          Enabling this will create a file {file}`/var/lib/glusterd/secure-access`.
           Disabling will delete this file again.
 
           See also: https://gluster.readthedocs.io/en/latest/Administrator%20Guide/SSL/
@@ -114,17 +114,17 @@ in
           options = {
             tlsKeyPath = mkOption {
               type = types.str;
-              description = "Path to the private key used for TLS.";
+              description = lib.mdDoc "Path to the private key used for TLS.";
             };
 
             tlsPem = mkOption {
               type = types.path;
-              description = "Path to the certificate used for TLS.";
+              description = lib.mdDoc "Path to the certificate used for TLS.";
             };
 
             caCert = mkOption {
               type = types.path;
-              description = "Path certificate authority used to sign the cluster certificates.";
+              description = lib.mdDoc "Path certificate authority used to sign the cluster certificates.";
             };
           };
         });
@@ -159,9 +159,10 @@ in
         install -m 0755 -d /var/log/glusterfs
       ''
       # The copying of hooks is due to upstream bug https://bugzilla.redhat.com/show_bug.cgi?id=1452761
+      # Excludes one hook due to missing SELinux binaries.
       + ''
         mkdir -p /var/lib/glusterd/hooks/
-        ${rsync}/bin/rsync -a ${glusterfs}/var/lib/glusterd/hooks/ /var/lib/glusterd/hooks/
+        ${rsync}/bin/rsync -a --exclude="S10selinux-label-brick.sh" ${glusterfs}/var/lib/glusterd/hooks/ /var/lib/glusterd/hooks/
 
         ${tlsCmd}
       ''
diff --git a/nixos/modules/services/network-filesystems/kbfs.nix b/nixos/modules/services/network-filesystems/kbfs.nix
index a43ac656f667..33ff283d5e81 100644
--- a/nixos/modules/services/network-filesystems/kbfs.nix
+++ b/nixos/modules/services/network-filesystems/kbfs.nix
@@ -15,15 +15,15 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to mount the Keybase filesystem.";
+        description = lib.mdDoc "Whether to mount the Keybase filesystem.";
       };
 
       enableRedirector = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Keybase root redirector service, allowing
-          any user to access KBFS files via <literal>/keybase</literal>,
+          any user to access KBFS files via `/keybase`,
           which will show different contents depending on the requester.
         '';
       };
@@ -32,7 +32,7 @@ in {
         type = types.str;
         default = "%h/keybase";
         example = "/keybase";
-        description = "Mountpoint for the Keybase filesystem.";
+        description = lib.mdDoc "Mountpoint for the Keybase filesystem.";
       };
 
       extraFlags = mkOption {
@@ -42,7 +42,7 @@ in {
           "-label kbfs"
           "-mount-type normal"
         ];
-        description = ''
+        description = lib.mdDoc ''
           Additional flags to pass to the Keybase filesystem on launch.
         '';
       };
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/kubo.nix
index 395b9788855f..13a062c32128 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/kubo.nix
@@ -1,9 +1,11 @@
 { config, lib, pkgs, utils, ... }:
 with lib;
 let
-  cfg = config.services.ipfs;
+  cfg = config.services.kubo;
 
-  ipfsFlags = utils.escapeSystemdExecArgs (
+  settingsFormat = pkgs.formats.json {};
+
+  kuboFlags = utils.escapeSystemdExecArgs (
     optional cfg.autoMount "--mount" ++
     optional cfg.enableGC "--enable-gc" ++
     optional (cfg.serviceFdlimit != null) "--manage-fdlimit=false" ++
@@ -50,27 +52,27 @@ in
 
   options = {
 
-    services.ipfs = {
+    services.kubo = {
 
-      enable = mkEnableOption "Interplanetary File System (WARNING: may cause severe network degredation)";
+      enable = mkEnableOption (lib.mdDoc "Interplanetary File System (WARNING: may cause severe network degradation)");
 
       package = mkOption {
         type = types.package;
-        default = pkgs.ipfs;
-        defaultText = literalExpression "pkgs.ipfs";
-        description = "Which IPFS package to use.";
+        default = pkgs.kubo;
+        defaultText = literalExpression "pkgs.kubo";
+        description = lib.mdDoc "Which Kubo package to use.";
       };
 
       user = mkOption {
         type = types.str;
         default = "ipfs";
-        description = "User under which the IPFS daemon runs";
+        description = lib.mdDoc "User under which the Kubo daemon runs";
       };
 
       group = mkOption {
         type = types.str;
         default = "ipfs";
-        description = "Group under which the IPFS daemon runs";
+        description = lib.mdDoc "Group under which the Kubo daemon runs";
       };
 
       dataDir = mkOption {
@@ -84,79 +86,83 @@ in
           then "/var/lib/ipfs"
           else "/var/lib/ipfs/.ipfs"
         '';
-        description = "The data dir for IPFS";
+        description = lib.mdDoc "The data dir for Kubo";
       };
 
       defaultMode = mkOption {
         type = types.enum [ "online" "offline" "norouting" ];
         default = "online";
-        description = "systemd service that is enabled by default";
+        description = lib.mdDoc "systemd service that is enabled by default";
       };
 
       autoMount = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
+        description = lib.mdDoc "Whether Kubo should try to mount /ipfs and /ipns at startup.";
       };
 
       autoMigrate = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether IPFS should try to run the fs-repo-migration at startup.";
+        description = lib.mdDoc "Whether Kubo should try to run the fs-repo-migration at startup.";
       };
 
       ipfsMountDir = mkOption {
         type = types.str;
         default = "/ipfs";
-        description = "Where to mount the IPFS namespace to";
+        description = lib.mdDoc "Where to mount the IPFS namespace to";
       };
 
       ipnsMountDir = mkOption {
         type = types.str;
         default = "/ipns";
-        description = "Where to mount the IPNS namespace to";
-      };
-
-      gatewayAddress = mkOption {
-        type = types.str;
-        default = "/ip4/127.0.0.1/tcp/8080";
-        description = "Where the IPFS Gateway can be reached";
-      };
-
-      apiAddress = mkOption {
-        type = types.str;
-        default = "/ip4/127.0.0.1/tcp/5001";
-        description = "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 = "Where IPFS listens for incoming p2p connections";
+        description = lib.mdDoc "Where to mount the IPNS namespace to";
       };
 
       enableGC = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable automatic garbage collection";
+        description = lib.mdDoc "Whether to enable automatic garbage collection";
       };
 
       emptyRepo = mkOption {
         type = types.bool;
         default = false;
-        description = "If set to true, the repo won't be initialized with help files";
+        description = lib.mdDoc "If set to true, the repo won't be initialized with help files";
       };
 
-      extraConfig = mkOption {
-        type = types.attrs;
-        description = ''
-          Attrset of daemon configuration to set using <command>ipfs config</command>, every time the daemon starts.
-          These are applied last, so may override configuration set by other options in this module.
+      settings = mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            Addresses.API = mkOption {
+              type = types.str;
+              default = "/ip4/127.0.0.1/tcp/5001";
+              description = lib.mdDoc "Where Kubo exposes its API to";
+            };
+
+            Addresses.Gateway = mkOption {
+              type = 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"
+                "/ip6/::/udp/4001/quic"
+              ];
+              description = lib.mdDoc "Where Kubo listens for incoming p2p connections";
+            };
+          };
+        };
+        description = lib.mdDoc ''
+          Attrset of daemon configuration to set using {command}`ipfs config`, every time the daemon starts.
+          See [https://github.com/ipfs/kubo/blob/master/docs/config.md](https://github.com/ipfs/kubo/blob/master/docs/config.md) for reference.
           Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
         '';
         default = { };
@@ -174,14 +180,14 @@ in
 
       extraFlags = mkOption {
         type = types.listOf types.str;
-        description = "Extra flags passed to the IPFS daemon";
+        description = lib.mdDoc "Extra flags passed to the Kubo daemon";
         default = [ ];
       };
 
       localDiscovery = mkOption {
         type = types.bool;
-        description = ''Whether to enable local discovery for the ipfs daemon.
-          This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
+        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;
       };
@@ -189,14 +195,14 @@ in
       serviceFdlimit = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
+        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 = "Whether to use socket activation to start IPFS when needed.";
+        description = lib.mdDoc "Whether to use socket activation to start Kubo when needed.";
       };
 
     };
@@ -223,7 +229,7 @@ in
         uid = config.ids.uids.ipfs;
         description = "IPFS daemon user";
         packages = [
-          pkgs.ipfs-migrator
+          pkgs.kubo-migrator
         ];
       };
     };
@@ -244,6 +250,12 @@ in
       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;
@@ -255,35 +267,23 @@ in
           # 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
+        ${pkgs.kubo-migrator}/bin/fs-repo-migrations -to '${cfg.package.repoVersion}' -y
       '' + ''
-          ipfs --offline config profile apply ${profile}
+          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
-              ))
+          | ${pkgs.jq}/bin/jq '. * $settings' --argjson settings ${
+              escapeShellArg (builtins.toJSON cfg.settings)
             } \
           | ipfs --offline config replace -
       '';
       serviceConfig = {
-        ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${ipfsFlags}" ];
+        ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ];
         User = cfg.user;
         Group = cfg.group;
         StateDirectory = "";
-        ReadWritePaths = [ "" cfg.dataDir ];
+        ReadWritePaths = optionals (!cfg.autoMount) [ "" cfg.dataDir ];
       } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
     } // optionalAttrs (!cfg.startWhenNeeded) {
       wantedBy = [ "default.target" ];
@@ -294,12 +294,12 @@ in
       socketConfig = {
         ListenStream =
           let
-            fromCfg = multiaddrToListenStream cfg.gatewayAddress;
+            fromCfg = multiaddrToListenStream cfg.settings.Addresses.Gateway;
           in
           [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
         ListenDatagram =
           let
-            fromCfg = multiaddrToListenDatagram cfg.gatewayAddress;
+            fromCfg = multiaddrToListenDatagram cfg.settings.Addresses.Gateway;
           in
           [ "" ] ++ lib.optional (fromCfg != null) fromCfg;
       };
@@ -311,7 +311,7 @@ in
       # in the multiaddr.
       socketConfig.ListenStream =
         let
-          fromCfg = multiaddrToListenStream cfg.apiAddress;
+          fromCfg = multiaddrToListenStream cfg.settings.Addresses.API;
         in
         [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg;
     };
@@ -320,4 +320,31 @@ in
   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/nixos/modules/services/network-filesystems/litestream/default.nix b/nixos/modules/services/network-filesystems/litestream/default.nix
index 51eb920d778d..884ffa50e7c6 100644
--- a/nixos/modules/services/network-filesystems/litestream/default.nix
+++ b/nixos/modules/services/network-filesystems/litestream/default.nix
@@ -8,18 +8,18 @@ let
 in
 {
   options.services.litestream = {
-    enable = mkEnableOption "litestream";
+    enable = mkEnableOption (lib.mdDoc "litestream");
 
     package = mkOption {
-      description = "Package to use.";
+      description = lib.mdDoc "Package to use.";
       default = pkgs.litestream;
       defaultText = literalExpression "pkgs.litestream";
       type = types.package;
     };
 
     settings = mkOption {
-      description = ''
-        See the <link xlink:href="https://litestream.io/reference/config/">documentation</link>.
+      description = lib.mdDoc ''
+        See the [documentation](https://litestream.io/reference/config/).
       '';
       type = settingsFormat.type;
       example = {
@@ -40,10 +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
@@ -56,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.
diff --git a/nixos/modules/services/network-filesystems/litestream/litestream.xml b/nixos/modules/services/network-filesystems/litestream/litestream.xml
index 598f9be8cf63..8f5597bb6891 100644
--- a/nixos/modules/services/network-filesystems/litestream/litestream.xml
+++ b/nixos/modules/services/network-filesystems/litestream/litestream.xml
@@ -15,7 +15,7 @@
   <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">
+   required permissions to access <link linkend="opt-services.grafana.settings.database.path">
    grafana database</link>:
 <programlisting>
 { pkgs, ... }:
diff --git a/nixos/modules/services/network-filesystems/moosefs.nix b/nixos/modules/services/network-filesystems/moosefs.nix
index 88b2ada37e75..ab82a2a07dd4 100644
--- a/nixos/modules/services/network-filesystems/moosefs.nix
+++ b/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" ];
@@ -75,26 +75,26 @@ in {
       masterHost = mkOption {
         type = types.str;
         default = null;
-        description = "IP or DNS name of master host.";
+        description = lib.mdDoc "IP or DNS name of master host.";
       };
 
       runAsUser = mkOption {
         type = types.bool;
         default = true;
         example = true;
-        description = "Run daemons as user moosefs instead of root.";
+        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 {
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Enable Moosefs master daemon.
 
-            You need to run <literal>mfsmaster-init</literal> on a freshly installed master server to
-            initialize the <literal>DATA_PATH</literal> direcory.
+            You need to run `mfsmaster-init` on a freshly installed master server to
+            initialize the `DATA_PATH` directory.
           '';
           default = false;
         };
@@ -102,7 +102,7 @@ in {
         exports = mkOption {
           type = with types; listOf str;
           default = null;
-          description = "Paths to export (see mfsexports.cfg).";
+          description = lib.mdDoc "Paths to export (see mfsexports.cfg).";
           example = [
             "* / rw,alldirs,admin,maproot=0:0"
             "* . rw"
@@ -111,7 +111,7 @@ in {
 
         openFirewall = mkOption {
           type = types.bool;
-          description = "Whether to automatically open the necessary ports in the firewall.";
+          description = lib.mdDoc "Whether to automatically open the necessary ports in the firewall.";
           default = false;
         };
 
@@ -122,16 +122,16 @@ in {
             options.DATA_PATH = mkOption {
               type = types.str;
               default = "/var/lib/mfs";
-              description = "Data storage directory.";
+              description = lib.mdDoc "Data storage directory.";
             };
           };
 
-          description = "Contents of config file (mfsmaster.cfg).";
+          description = lib.mdDoc "Contents of config file (mfsmaster.cfg).";
         };
       };
 
       metalogger = {
-        enable = mkEnableOption "Moosefs metalogger daemon.";
+        enable = mkEnableOption (lib.mdDoc "Moosefs metalogger daemon.");
 
         settings = mkOption {
           type = types.submodule {
@@ -140,27 +140,27 @@ in {
             options.DATA_PATH = mkOption {
               type = types.str;
               default = "/var/lib/mfs";
-              description = "Data storage directory";
+              description = lib.mdDoc "Data storage directory";
             };
           };
 
-          description = "Contents of metalogger config file (mfsmetalogger.cfg).";
+          description = lib.mdDoc "Contents of metalogger config file (mfsmetalogger.cfg).";
         };
       };
 
       chunkserver = {
-        enable = mkEnableOption "Moosefs chunkserver daemon.";
+        enable = mkEnableOption (lib.mdDoc "Moosefs chunkserver daemon.");
 
         openFirewall = mkOption {
           type = types.bool;
-          description = "Whether to automatically open the necessary ports in the firewall.";
+          description = lib.mdDoc "Whether to automatically open the necessary ports in the firewall.";
           default = false;
         };
 
         hdds = mkOption {
           type = with types; listOf str;
           default =  null;
-          description = "Mount points to be used by chunkserver for storage (see mfshdd.cfg).";
+          description = lib.mdDoc "Mount points to be used by chunkserver for storage (see mfshdd.cfg).";
           example = [ "/mnt/hdd1" ];
         };
 
@@ -171,11 +171,11 @@ in {
             options.DATA_PATH = mkOption {
               type = types.str;
               default = "/var/lib/mfs";
-              description = "Directory for lock file.";
+              description = lib.mdDoc "Directory for lock file.";
             };
           };
 
-          description = "Contents of chunkserver config file (mfschunkserver.cfg).";
+          description = lib.mdDoc "Contents of chunkserver config file (mfschunkserver.cfg).";
         };
       };
     };
diff --git a/nixos/modules/services/network-filesystems/netatalk.nix b/nixos/modules/services/network-filesystems/netatalk.nix
index 06a36eb30c29..a40f68557c0e 100644
--- a/nixos/modules/services/network-filesystems/netatalk.nix
+++ b/nixos/modules/services/network-filesystems/netatalk.nix
@@ -10,12 +10,12 @@ in {
   options = {
     services.netatalk = {
 
-      enable = mkEnableOption "the Netatalk AFP fileserver";
+      enable = mkEnableOption (lib.mdDoc "the Netatalk AFP fileserver");
 
       port = mkOption {
         type = types.port;
         default = 548;
-        description = "TCP port to be used for AFP.";
+        description = lib.mdDoc "TCP port to be used for AFP.";
       };
 
       settings = mkOption {
@@ -32,20 +32,18 @@ in {
             "read only" = true;
           };
         };
-        description = ''
+        description = lib.mdDoc ''
           Configuration for Netatalk. See
-          <citerefentry><refentrytitle>afp.conf</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry>.
+          {manpage}`afp.conf(5)`.
         '';
       };
 
       extmap = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           File name extension mappings.
-          See <citerefentry><refentrytitle>extmap.conf</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry>. for more information.
+          See {manpage}`extmap.conf(5)`. for more information.
         '';
       };
 
diff --git a/nixos/modules/services/network-filesystems/nfsd.nix b/nixos/modules/services/network-filesystems/nfsd.nix
index 1b62bfa82035..c9e1cbcbbda4 100644
--- a/nixos/modules/services/network-filesystems/nfsd.nix
+++ b/nixos/modules/services/network-filesystems/nfsd.nix
@@ -26,7 +26,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable the kernel's NFS server.
           '';
         };
@@ -34,7 +34,7 @@ in
         extraNfsdConfig = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Extra configuration options for the [nfsd] section of /etc/nfs.conf.
           '';
         };
@@ -42,28 +42,26 @@ in
         exports = mkOption {
           type = types.lines;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Contents of the /etc/exports file.  See
-            <citerefentry><refentrytitle>exports</refentrytitle>
-            <manvolnum>5</manvolnum></citerefentry> for the format.
+            {manpage}`exports(5)` for the format.
           '';
         };
 
         hostName = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Hostname or address on which NFS requests will be accepted.
-            Default is all.  See the <option>-H</option> option in
-            <citerefentry><refentrytitle>nfsd</refentrytitle>
-            <manvolnum>8</manvolnum></citerefentry>.
+            Default is all.  See the {option}`-H` option in
+            {manpage}`nfsd(8)`.
           '';
         };
 
         nproc = mkOption {
           type = types.int;
           default = 8;
-          description = ''
+          description = lib.mdDoc ''
             Number of NFS server threads.  Defaults to the recommended value of 8.
           '';
         };
@@ -71,14 +69,14 @@ in
         createMountPoints = mkOption {
           type = types.bool;
           default = false;
-          description = "Whether to create the mount points in the exports file at startup time.";
+          description = lib.mdDoc "Whether to create the mount points in the exports file at startup time.";
         };
 
         mountdPort = mkOption {
           type = types.nullOr types.int;
           default = null;
           example = 4002;
-          description = ''
+          description = lib.mdDoc ''
             Use fixed port for rpc.mountd, useful if server is behind firewall.
           '';
         };
@@ -87,9 +85,9 @@ in
           type = types.nullOr types.int;
           default = null;
           example = 4001;
-          description = ''
+          description = lib.mdDoc ''
             Use a fixed port for the NFS lock manager kernel module
-            (<literal>lockd/nlockmgr</literal>).  This is useful if the
+            (`lockd/nlockmgr`).  This is useful if the
             NFS server is behind a firewall.
           '';
         };
@@ -98,8 +96,8 @@ in
           type = types.nullOr types.int;
           default = null;
           example = 4000;
-          description = ''
-            Use a fixed port for <command>rpc.statd</command>. This is
+          description = lib.mdDoc ''
+            Use a fixed port for {command}`rpc.statd`. This is
             useful if the NFS server is behind a firewall.
           '';
         };
diff --git a/nixos/modules/services/network-filesystems/openafs/client.nix b/nixos/modules/services/network-filesystems/openafs/client.nix
index c8cc5052c2ac..bb0fee087e62 100644
--- a/nixos/modules/services/network-filesystems/openafs/client.nix
+++ b/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -33,29 +33,29 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to enable the OpenAFS client.";
+        description = lib.mdDoc "Whether to enable the OpenAFS client.";
       };
 
       afsdb = mkOption {
         default = true;
         type = types.bool;
-        description = "Resolve cells via AFSDB DNS records.";
+        description = lib.mdDoc "Resolve cells via AFSDB DNS records.";
       };
 
       cellName = mkOption {
         default = "";
         type = types.str;
-        description = "Cell name.";
+        description = lib.mdDoc "Cell name.";
         example = "grand.central.org";
       };
 
       cellServDB = mkOption {
         default = [];
         type = with types; listOf (submodule { options = cellServDBConfig; });
-        description = ''
+        description = lib.mdDoc ''
           This cell's database server records, added to the global
           CellServDB. See CellServDB(5) man page for syntax. Ignored when
-          <literal>afsdb</literal> is set to <literal>true</literal>.
+          `afsdb` is set to `true`.
         '';
         example = [
           { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; }
@@ -67,15 +67,15 @@ in
         blocks = mkOption {
           default = 100000;
           type = types.int;
-          description = "Cache size in 1KB blocks.";
+          description = lib.mdDoc "Cache size in 1KB blocks.";
         };
 
         chunksize = mkOption {
           default = 0;
           type = types.ints.between 0 30;
-          description = ''
+          description = lib.mdDoc ''
             Size of each cache chunk given in powers of
-            2. <literal>0</literal> resets the chunk size to its default
+            2. `0` resets the chunk size to its default
             values (13 (8 KB) for memcache, 18-20 (256 KB to 1 MB) for
             diskcache). Maximum value is 30. Important performance
             parameter. Set to higher values when dealing with large files.
@@ -85,13 +85,13 @@ in
         directory = mkOption {
           default = "/var/cache/openafs";
           type = types.str;
-          description = "Cache directory.";
+          description = lib.mdDoc "Cache directory.";
         };
 
         diskless = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Use in-memory cache for diskless machines. Has no real
             performance benefit anymore.
           '';
@@ -101,13 +101,13 @@ in
       crypt = mkOption {
         default = true;
         type = types.bool;
-        description = "Whether to enable (weak) protocol encryption.";
+        description = lib.mdDoc "Whether to enable (weak) protocol encryption.";
       };
 
       daemons = mkOption {
         default = 2;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Number of daemons to serve user requests. Numbers higher than 6
           usually do no increase performance. Default is sufficient for up
           to five concurrent users.
@@ -117,9 +117,9 @@ in
       fakestat = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Return fake data on stat() calls. If <literal>true</literal>,
-          always do so. If <literal>false</literal>, only do so for
+        description = lib.mdDoc ''
+          Return fake data on stat() calls. If `true`,
+          always do so. If `false`, only do so for
           cross-cell mounts (as these are potentially expensive).
         '';
       };
@@ -127,9 +127,9 @@ in
       inumcalc = mkOption {
         default = "compat";
         type = types.strMatching "compat|md5";
-        description = ''
-          Inode calculation method. <literal>compat</literal> is
-          computationally less expensive, but <literal>md5</literal> greatly
+        description = lib.mdDoc ''
+          Inode calculation method. `compat` is
+          computationally less expensive, but `md5` greatly
           reduces the likelihood of inode collisions in larger scenarios
           involving multiple cells mounted into one AFS space.
         '';
@@ -138,9 +138,9 @@ in
       mountPoint = mkOption {
         default = "/afs";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Mountpoint of the AFS file tree, conventionally
-          <literal>/afs</literal>. When set to a different value, only
+          `/afs`. When set to a different value, only
           cross-cells that use the same value can be accessed.
         '';
       };
@@ -150,28 +150,28 @@ in
           default = config.boot.kernelPackages.openafs;
           defaultText = literalExpression "config.boot.kernelPackages.openafs";
           type = types.package;
-          description = "OpenAFS kernel module package. MUST match the userland package!";
+          description = lib.mdDoc "OpenAFS kernel module package. MUST match the userland package!";
         };
         programs = mkOption {
           default = getBin pkgs.openafs;
           defaultText = literalExpression "getBin pkgs.openafs";
           type = types.package;
-          description = "OpenAFS programs package. MUST match the kernel module package!";
+          description = lib.mdDoc "OpenAFS programs package. MUST match the kernel module package!";
         };
       };
 
       sparse = mkOption {
         default = true;
         type = types.bool;
-        description = "Minimal cell list in /afs.";
+        description = lib.mdDoc "Minimal cell list in /afs.";
       };
 
       startDisconnected = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Start up in disconnected mode.  You need to execute
-          <literal>fs disco online</literal> (as root) to switch to
+          `fs disco online` (as root) to switch to
           connected mode. Useful for roaming devices.
         '';
       };
diff --git a/nixos/modules/services/network-filesystems/openafs/lib.nix b/nixos/modules/services/network-filesystems/openafs/lib.nix
index e068ee761c2a..80628f4dfaf2 100644
--- a/nixos/modules/services/network-filesystems/openafs/lib.nix
+++ b/nixos/modules/services/network-filesystems/openafs/lib.nix
@@ -17,13 +17,13 @@ in {
       type = types.str;
       default = "";
       example = "1.2.3.4";
-      description = "IP Address of a database server";
+      description = lib.mdDoc "IP Address of a database server";
     };
     dnsname = mkOption {
       type = types.str;
       default = "";
       example = "afs.example.org";
-      description = "DNS full-qualified domain name of a database server";
+      description = lib.mdDoc "DNS full-qualified domain name of a database server";
     };
   };
 
diff --git a/nixos/modules/services/network-filesystems/openafs/server.nix b/nixos/modules/services/network-filesystems/openafs/server.nix
index 9c974335defa..1c615d3bfb64 100644
--- a/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -49,13 +49,13 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the OpenAFS server. An OpenAFS server needs a
           complex setup. So, be aware that enabling this service and setting
           some options does not give you a turn-key-ready solution. You need
           at least a running Kerberos 5 setup, as OpenAFS relies on it for
           authentication. See the Guide "QuickStartUnix" coming with
-          <literal>pkgs.openafs.doc</literal> for complete setup
+          `pkgs.openafs.doc` for complete setup
           instructions.
         '';
       };
@@ -63,27 +63,27 @@ in {
       advertisedAddresses = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "List of IP addresses this server is advertised under. See NetInfo(5)";
+        description = lib.mdDoc "List of IP addresses this server is advertised under. See NetInfo(5)";
       };
 
       cellName = mkOption {
         default = "";
         type = types.str;
-        description = "Cell name, this server will serve.";
+        description = lib.mdDoc "Cell name, this server will serve.";
         example = "grand.central.org";
       };
 
       cellServDB = mkOption {
         default = [];
         type = with types; listOf (submodule [ { options = cellServDBConfig;} ]);
-        description = "Definition of all cell-local database server machines.";
+        description = lib.mdDoc "Definition of all cell-local database server machines.";
       };
 
       package = mkOption {
         default = pkgs.openafs.server or pkgs.openafs;
         defaultText = literalExpression "pkgs.openafs.server or pkgs.openafs";
         type = types.package;
-        description = "OpenAFS package for the server binaries";
+        description = lib.mdDoc "OpenAFS package for the server binaries";
       };
 
       roles = {
@@ -91,33 +91,33 @@ in {
           enable = mkOption {
             default = true;
             type = types.bool;
-            description = "Fileserver role, serves files and volumes from its local storage.";
+            description = lib.mdDoc "Fileserver role, serves files and volumes from its local storage.";
           };
 
           fileserverArgs = mkOption {
             default = "-vattachpar 128 -vhashsize 11 -L -rxpck 400 -cb 1000000";
             type = types.str;
-            description = "Arguments to the dafileserver process. See its man page.";
+            description = lib.mdDoc "Arguments to the dafileserver process. See its man page.";
           };
 
           volserverArgs = mkOption {
             default = "";
             type = types.str;
-            description = "Arguments to the davolserver process. See its man page.";
+            description = lib.mdDoc "Arguments to the davolserver process. See its man page.";
             example = "-sync never";
           };
 
           salvageserverArgs = mkOption {
             default = "";
             type = types.str;
-            description = "Arguments to the salvageserver process. See its man page.";
+            description = lib.mdDoc "Arguments to the salvageserver process. See its man page.";
             example = "-showlog";
           };
 
           salvagerArgs = mkOption {
             default = "";
             type = types.str;
-            description = "Arguments to the dasalvager process. See its man page.";
+            description = lib.mdDoc "Arguments to the dasalvager process. See its man page.";
             example = "-showlog -showmounts";
           };
         };
@@ -126,10 +126,10 @@ in {
           enable = mkOption {
             default = true;
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Database server role, maintains the Volume Location Database,
               Protection Database (and Backup Database, see
-              <literal>backup</literal> role). There can be multiple
+              `backup` role). There can be multiple
               servers in the database role for replication, which then need
               reliable network connection to each other.
 
@@ -141,14 +141,14 @@ in {
           vlserverArgs = mkOption {
             default = "";
             type = types.str;
-            description = "Arguments to the vlserver process. See its man page.";
+            description = lib.mdDoc "Arguments to the vlserver process. See its man page.";
             example = "-rxbind";
           };
 
           ptserverArgs = mkOption {
             default = "";
             type = types.str;
-            description = "Arguments to the ptserver process. See its man page.";
+            description = lib.mdDoc "Arguments to the ptserver process. See its man page.";
             example = "-restricted -default_access S---- S-M---";
           };
         };
@@ -157,9 +157,9 @@ in {
           enable = mkOption {
             default = false;
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Backup server role. Use in conjunction with the
-              <literal>database</literal> role to maintain the Backup
+              `database` role to maintain the Backup
               Database. Normally only used in conjunction with tape storage
               or IBM's Tivoli Storage Manager.
             '';
@@ -168,14 +168,14 @@ in {
           buserverArgs = mkOption {
             default = "";
             type = types.str;
-            description = "Arguments to the buserver process. See its man page.";
+            description = lib.mdDoc "Arguments to the buserver process. See its man page.";
             example = "-p 8";
           };
 
           cellServDB = mkOption {
             default = [];
             type = with types; listOf (submodule [ { options = cellServDBConfig;} ]);
-            description = ''
+            description = lib.mdDoc ''
               Definition of all cell-local backup database server machines.
               Use this when your cell uses less backup database servers than
               other database server machines.
@@ -187,7 +187,7 @@ in {
       dottedPrincipals= mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, allow principal names containing (.) dots. Enabling
           this has security implications!
         '';
@@ -196,11 +196,11 @@ in {
       udpPacketSize = mkOption {
         default = 1310720;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           UDP packet size to use in Bytes. Higher values can speed up
           communications. The default of 1 MB is a sufficient in most
           cases. Make sure to increase the kernel's UDP buffer size
-          accordingly via <literal>net.core(w|r|opt)mem_max</literal>
+          accordingly via `net.core(w|r|opt)mem_max`
           sysctl.
         '';
       };
diff --git a/nixos/modules/services/network-filesystems/orangefs/client.nix b/nixos/modules/services/network-filesystems/orangefs/client.nix
index 36ea5af2168d..68f23f477af1 100644
--- a/nixos/modules/services/network-filesystems/orangefs/client.nix
+++ b/nixos/modules/services/network-filesystems/orangefs/client.nix
@@ -10,18 +10,18 @@ in {
 
   options = {
     services.orangefs.client = {
-      enable = mkEnableOption "OrangeFS client daemon";
+      enable = mkEnableOption (lib.mdDoc "OrangeFS client daemon");
 
       extraOptions = mkOption {
         type = with types; listOf str;
         default = [];
-        description = "Extra command line options for pvfs2-client.";
+        description = lib.mdDoc "Extra command line options for pvfs2-client.";
       };
 
       fileSystems = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The orangefs file systems to be mounted.
-          This option is prefered over using <option>fileSystems</option> 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.
         '';
 
@@ -36,19 +36,19 @@ in {
             mountPoint = mkOption {
               type = types.str;
               default = "/orangefs";
-              description = "Mount point.";
+              description = lib.mdDoc "Mount point.";
             };
 
             options = mkOption {
               type = with types; listOf str;
               default = [];
-              description = "Mount options";
+              description = lib.mdDoc "Mount options";
             };
 
             target = mkOption {
               type = types.str;
               example = "tcp://server:3334/orangefs";
-              description = "Target URL";
+              description = lib.mdDoc "Target URL";
             };
           };
         }));
diff --git a/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixos/modules/services/network-filesystems/orangefs/server.nix
index 621c2fe8f78d..e20e7975ebaa 100644
--- a/nixos/modules/services/network-filesystems/orangefs/server.nix
+++ b/nixos/modules/services/network-filesystems/orangefs/server.nix
@@ -74,45 +74,45 @@ in {
 
   options = {
     services.orangefs.server = {
-      enable = mkEnableOption "OrangeFS server";
+      enable = mkEnableOption (lib.mdDoc "OrangeFS server");
 
       logType = mkOption {
         type = with types; enum [ "file" "syslog" ];
         default = "syslog";
-        description = "Destination for log messages.";
+        description = lib.mdDoc "Destination for log messages.";
       };
 
       dataStorageSpace = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/data/storage";
-        description = "Directory for data storage.";
+        description = lib.mdDoc "Directory for data storage.";
       };
 
       metadataStorageSpace = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/data/meta";
-        description = "Directory for meta data storage.";
+        description = lib.mdDoc "Directory for meta data storage.";
       };
 
       BMIModules = mkOption {
         type = with types; listOf str;
         default = [ "bmi_tcp" ];
         example = [ "bmi_tcp" "bmi_ib"];
-        description = "List of BMI modules to load.";
+        description = lib.mdDoc "List of BMI modules to load.";
       };
 
       extraDefaults = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra config for <literal>&lt;Defaults&gt;</literal> section.";
+        description = lib.mdDoc "Extra config for `<Defaults>` section.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra config for the global section.";
+        description = lib.mdDoc "Extra config for the global section.";
       };
 
       servers = mkOption {
@@ -122,12 +122,12 @@ in {
           node1 = "tcp://node1:3334";
           node2 = "tcp://node2:3334";
         };
-        description = "URLs for storage server including port. The attribute names define the server alias.";
+        description = lib.mdDoc "URLs for storage server including port. The attribute names define the server alias.";
       };
 
       fileSystems = mkOption {
-        description = ''
-          These options will create the <literal>&lt;FileSystem&gt;</literal> sections of config file.
+        description = lib.mdDoc ''
+          These options will create the `<FileSystem>` sections of config file.
         '';
         default = { orangefs = {}; };
         example = literalExpression ''
@@ -146,37 +146,37 @@ in {
             id = mkOption {
               type = types.int;
               default = 1;
-              description = "File system ID (must be unique within configuration).";
+              description = lib.mdDoc "File system ID (must be unique within configuration).";
             };
 
             rootHandle = mkOption {
               type = types.int;
               default = 3;
-              description = "File system root ID.";
+              description = lib.mdDoc "File system root ID.";
             };
 
             extraConfig = mkOption {
               type = types.lines;
               default = "";
-              description = "Extra config for <literal>&lt;FileSystem&gt;</literal> section.";
+              description = lib.mdDoc "Extra config for `<FileSystem>` section.";
             };
 
             troveSyncMeta = mkOption {
               type = types.bool;
               default = true;
-              description = "Sync meta data.";
+              description = lib.mdDoc "Sync meta data.";
             };
 
             troveSyncData = mkOption {
               type = types.bool;
               default = false;
-              description = "Sync data.";
+              description = lib.mdDoc "Sync data.";
             };
 
             extraStorageHints = mkOption {
               type = types.lines;
               default = "";
-              description = "Extra config for <literal>&lt;StorageHints&gt;</literal> section.";
+              description = lib.mdDoc "Extra config for `<StorageHints>` section.";
             };
           };
         }));
@@ -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/nixos/modules/services/network-filesystems/rsyncd.nix b/nixos/modules/services/network-filesystems/rsyncd.nix
index e72f9b54cd6f..c9d7475395fe 100644
--- a/nixos/modules/services/network-filesystems/rsyncd.nix
+++ b/nixos/modules/services/network-filesystems/rsyncd.nix
@@ -10,12 +10,12 @@ in {
   options = {
     services.rsyncd = {
 
-      enable = mkEnableOption "the rsync daemon";
+      enable = mkEnableOption (lib.mdDoc "the rsync daemon");
 
       port = mkOption {
         default = 873;
         type = types.port;
-        description = "TCP port the daemon will listen on.";
+        description = lib.mdDoc "TCP port the daemon will listen on.";
       };
 
       settings = mkOption {
@@ -39,10 +39,9 @@ in {
             "secrets file" = "/etc/rsyncd.secrets";
           };
         };
-        description = ''
+        description = lib.mdDoc ''
           Configuration for rsyncd. See
-          <citerefentry><refentrytitle>rsyncd.conf</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry>.
+          {manpage}`rsyncd.conf(5)`.
         '';
       };
 
@@ -50,7 +49,7 @@ in {
         default = false;
         type = types.bool;
         description =
-          "If enabled Rsync will be socket-activated rather than run persistently.";
+          lib.mdDoc "If enabled Rsync will be socket-activated rather than run persistently.";
       };
 
     };
diff --git a/nixos/modules/services/network-filesystems/samba-wsdd.nix b/nixos/modules/services/network-filesystems/samba-wsdd.nix
index 800ef448d376..24407f05de6a 100644
--- a/nixos/modules/services/network-filesystems/samba-wsdd.nix
+++ b/nixos/modules/services/network-filesystems/samba-wsdd.nix
@@ -8,61 +8,61 @@ 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;
         example = "eth0";
-        description = "Interface or address to use.";
+        description = lib.mdDoc "Interface or address to use.";
       };
       hoplimit = mkOption {
         type = types.nullOr types.int;
         default = null;
         example = 2;
-        description = "Hop limit for multicast packets (default = 1).";
+        description = lib.mdDoc "Hop limit for multicast packets (default = 1).";
       };
       workgroup = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "HOME";
-        description = "Set workgroup name (default WORKGROUP).";
+        description = lib.mdDoc "Set workgroup name (default WORKGROUP).";
       };
       hostname = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "FILESERVER";
-        description = "Override (NetBIOS) hostname to be used (default hostname).";
+        description = lib.mdDoc "Override (NetBIOS) hostname to be used (default hostname).";
       };
       domain = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Set domain name (disables workgroup).";
+        description = lib.mdDoc "Set domain name (disables workgroup).";
       };
       discovery = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable discovery operation mode.";
+        description = lib.mdDoc "Enable discovery operation mode.";
       };
       listen = mkOption {
         type = types.str;
         default = "/run/wsdd/wsdd.sock";
-        description = "Listen on path or localhost port in discovery mode.";
+        description = lib.mdDoc "Listen on path or localhost port in discovery mode.";
       };
       extraOptions = mkOption {
         type = types.listOf types.str;
         default = [ "--shortlog" ];
         example = [ "--verbose" "--no-http" "--ipv4only" "--no-host" ];
-        description = "Additional wsdd options.";
+        description = lib.mdDoc "Additional wsdd options.";
       };
     };
   };
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 992f948e8cd5..1310a374abd0 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -80,23 +80,22 @@ 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;
+          :::
         '';
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to automatically open the necessary ports in the firewall.
         '';
       };
@@ -104,7 +103,7 @@ in
       enableNmbd = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Samba's nmbd, which replies to NetBIOS over IP name
           service requests. It also participates in the browsing protocols
           which make up the Windows "Network Neighborhood" view.
@@ -114,7 +113,7 @@ in
       enableWinbindd = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Samba's winbindd, which provides a number of services
           to the Name Service Switch capability found in most modern C libraries,
           to arbitrary applications via PAM and ntlm_auth and to Samba itself.
@@ -126,7 +125,7 @@ in
         default = pkgs.samba;
         defaultText = literalExpression "pkgs.samba";
         example = literalExpression "pkgs.samba4Full";
-        description = ''
+        description = lib.mdDoc ''
           Defines which package should be used for the samba server.
         '';
       };
@@ -134,7 +133,7 @@ in
       invalidUsers = mkOption {
         type = types.listOf types.str;
         default = [ "root" ];
-        description = ''
+        description = lib.mdDoc ''
           List of users who are denied to login via Samba.
         '';
       };
@@ -142,7 +141,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Additional global section and extra section lines go in here.
         '';
         example = ''
@@ -154,7 +153,7 @@ in
       configText = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Verbatim contents of smb.conf. If null (default), use the
           autogenerated file from NixOS instead.
         '';
@@ -163,13 +162,13 @@ in
       securityType = mkOption {
         type = types.str;
         default = "user";
-        description = "Samba security type";
+        description = lib.mdDoc "Samba security type";
       };
 
       nsswins = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the WINS NSS (Name Service Switch) plug-in.
           Enabling it allows applications to resolve WINS/NetBIOS names (a.k.a.
           Windows machine names) by transparently querying the winbindd daemon.
@@ -178,9 +177,9 @@ in
 
       shares = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           A set describing shared resources.
-          See <command>man smb.conf</command> for options.
+          See {command}`man smb.conf` for options.
         '';
         type = types.attrsOf (types.attrsOf types.unspecified);
         example = literalExpression ''
diff --git a/nixos/modules/services/network-filesystems/tahoe.nix b/nixos/modules/services/network-filesystems/tahoe.nix
index 5426463dffac..14c0a3d4725f 100644
--- a/nixos/modules/services/network-filesystems/tahoe.nix
+++ b/nixos/modules/services/network-filesystems/tahoe.nix
@@ -12,21 +12,21 @@ in
           options = {
             nickname = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The nickname of this Tahoe introducer.
               '';
             };
             tub.port = mkOption {
               default = 3458;
-              type = types.int;
-              description = ''
+              type = types.port;
+              description = lib.mdDoc ''
                 The port on which the introducer will listen.
               '';
             };
             tub.location = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The external location that the introducer should listen on.
 
                 If specified, the port should be included.
@@ -36,13 +36,13 @@ in
               default = pkgs.tahoelafs;
               defaultText = literalExpression "pkgs.tahoelafs";
               type = types.package;
-              description = ''
+              description = lib.mdDoc ''
                 The package to use for the Tahoe LAFS daemon.
               '';
             };
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           The Tahoe introducers.
         '';
       };
@@ -52,14 +52,14 @@ in
           options = {
             nickname = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The nickname of this Tahoe node.
               '';
             };
             tub.port = mkOption {
               default = 3457;
-              type = types.int;
-              description = ''
+              type = types.port;
+              description = lib.mdDoc ''
                 The port on which the tub will listen.
 
                 This is the correct setting to tweak if you want Tahoe's storage
@@ -69,7 +69,7 @@ in
             tub.location = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The external location that the node should listen on.
 
                 This is the setting to tweak if there are multiple interfaces
@@ -80,8 +80,8 @@ in
             };
             web.port = mkOption {
               default = 3456;
-              type = types.int;
-              description = ''
+              type = types.port;
+              description = lib.mdDoc ''
                 The port on which the Web server will listen.
 
                 This is the correct setting to tweak if you want Tahoe's WUI to
@@ -91,7 +91,7 @@ in
             client.introducer = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The furl for a Tahoe introducer node.
 
                 Like all furls, keep this safe and don't share it.
@@ -100,7 +100,7 @@ in
             client.helper = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The furl for a Tahoe helper node.
 
                 Like all furls, keep this safe and don't share it.
@@ -109,14 +109,14 @@ in
             client.shares.needed = mkOption {
               default = 3;
               type = types.int;
-              description = ''
+              description = lib.mdDoc ''
                 The number of shares required to reconstitute a file.
               '';
             };
             client.shares.happy = mkOption {
               default = 7;
               type = types.int;
-              description = ''
+              description = lib.mdDoc ''
                 The number of distinct storage nodes required to store
                 a file.
               '';
@@ -124,24 +124,24 @@ in
             client.shares.total = mkOption {
               default = 10;
               type = types.int;
-              description = ''
+              description = lib.mdDoc ''
                 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;
-              description = ''
+              description = lib.mdDoc ''
                 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;
-              description = ''
+              description = lib.mdDoc ''
                 The port on which the SFTP server will listen.
 
                 This is the correct setting to tweak if you want Tahoe's SFTP
@@ -151,28 +151,28 @@ in
             sftpd.hostPublicKeyFile = mkOption {
               default = null;
               type = types.nullOr types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path to the SSH host public key.
               '';
             };
             sftpd.hostPrivateKeyFile = mkOption {
               default = null;
               type = types.nullOr types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path to the SSH host private key.
               '';
             };
             sftpd.accounts.file = mkOption {
               default = null;
               type = types.nullOr types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path to the accounts file.
               '';
             };
             sftpd.accounts.url = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 URL of the accounts server.
               '';
             };
@@ -180,13 +180,13 @@ in
               default = pkgs.tahoelafs;
               defaultText = literalExpression "pkgs.tahoelafs";
               type = types.package;
-              description = ''
+              description = lib.mdDoc ''
                 The package to use for the Tahoe LAFS daemon.
               '';
             };
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           The Tahoe nodes.
         '';
       };
diff --git a/nixos/modules/services/network-filesystems/u9fs.nix b/nixos/modules/services/network-filesystems/u9fs.nix
index 77961b78cadb..d6968b2cb826 100644
--- a/nixos/modules/services/network-filesystems/u9fs.nix
+++ b/nixos/modules/services/network-filesystems/u9fs.nix
@@ -14,16 +14,16 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to run the u9fs 9P server for Unix.";
+        description = lib.mdDoc "Whether to run the u9fs 9P server for Unix.";
       };
 
       listenStreams = mkOption {
         type = types.listOf types.str;
         default = [ "564" ];
         example = [ "192.168.16.1:564" ];
-        description = ''
+        description = lib.mdDoc ''
           Sockets to listen for clients on.
-          See <command>man 5 systemd.socket</command> for socket syntax.
+          See {command}`man 5 systemd.socket` for socket syntax.
         '';
       };
 
@@ -31,7 +31,7 @@ in
         type = types.str;
         default = "nobody";
         description =
-          "User to run u9fs under.";
+          lib.mdDoc "User to run u9fs under.";
       };
 
       extraArgs = mkOption {
@@ -39,9 +39,9 @@ in
         default = "";
         example = "-a none";
         description =
-          ''
+          lib.mdDoc ''
             Extra arguments to pass on invocation,
-            see <command>man 4 u9fs</command>
+            see {command}`man 4 u9fs`
           '';
       };
 
diff --git a/nixos/modules/services/network-filesystems/webdav-server-rs.nix b/nixos/modules/services/network-filesystems/webdav-server-rs.nix
index 1c5c299cb673..9ea304111819 100644
--- a/nixos/modules/services/network-filesystems/webdav-server-rs.nix
+++ b/nixos/modules/services/network-filesystems/webdav-server-rs.nix
@@ -14,27 +14,27 @@ in
 {
   options = {
     services.webdav-server-rs = {
-      enable = mkEnableOption "WebDAV server";
+      enable = mkEnableOption (lib.mdDoc "WebDAV server");
 
       user = mkOption {
         type = types.str;
         default = "webdav";
-        description = "User to run under when setuid is not enabled.";
+        description = lib.mdDoc "User to run under when setuid is not enabled.";
       };
 
       group = mkOption {
         type = types.str;
         default = "webdav";
-        description = "Group to run under when setuid is not enabled.";
+        description = lib.mdDoc "Group to run under when setuid is not enabled.";
       };
 
       settings = mkOption {
         type = format.type;
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           Attrset that is converted and passed as config file. Available
           options can be found at
-          <link xlink:href="https://github.com/miquels/webdav-server-rs/blob/master/webdav-server.toml">here</link>.
+          [here](https://github.com/miquels/webdav-server-rs/blob/master/webdav-server.toml).
         '';
         example = literalExpression ''
           {
@@ -73,7 +73,7 @@ in
         type = types.path;
         default = format.generate "webdav-server.toml" settings;
         defaultText = "Config file generated from services.webdav-server-rs.settings";
-        description = ''
+        description = lib.mdDoc ''
           Path to config file. If this option is set, it will override any
           configuration done in services.webdav-server-rs.settings.
         '';
diff --git a/nixos/modules/services/network-filesystems/webdav.nix b/nixos/modules/services/network-filesystems/webdav.nix
index a810af40fd47..a384e58c96bf 100644
--- a/nixos/modules/services/network-filesystems/webdav.nix
+++ b/nixos/modules/services/network-filesystems/webdav.nix
@@ -8,32 +8,32 @@ in
 {
   options = {
     services.webdav = {
-      enable = mkEnableOption "WebDAV server";
+      enable = mkEnableOption (lib.mdDoc "WebDAV server");
 
       user = mkOption {
         type = types.str;
         default = "webdav";
-        description = "User account under which WebDAV runs.";
+        description = lib.mdDoc "User account under which WebDAV runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "webdav";
-        description = "Group under which WebDAV runs.";
+        description = lib.mdDoc "Group under which WebDAV runs.";
       };
 
       settings = mkOption {
         type = format.type;
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           Attrset that is converted and passed as config file. Available options
           can be found at
-          <link xlink:href="https://github.com/hacdias/webdav">here</link>.
+          [here](https://github.com/hacdias/webdav).
 
           This program supports reading username and password configuration
           from environment variables, so it's strongly recommended to store
           username and password in a separate
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#EnvironmentFile=">EnvironmentFile</link>.
+          [EnvironmentFile](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#EnvironmentFile=).
           This prevents adding secrets to the world-readable Nix store.
         '';
         example = literalExpression ''
@@ -57,7 +57,7 @@ in
         type = types.path;
         default = format.generate "webdav.yaml" cfg.settings;
         defaultText = "Config file generated from services.webdav.settings";
-        description = ''
+        description = lib.mdDoc ''
           Path to config file. If this option is set, it will override any
           configuration done in options.services.webdav.settings.
         '';
@@ -67,10 +67,8 @@ 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)`.
         '';
       };
     };
diff --git a/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixos/modules/services/network-filesystems/xtreemfs.nix
index fc0723115787..926c3c3bd523 100644
--- a/nixos/modules/services/network-filesystems/xtreemfs.nix
+++ b/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -89,12 +89,12 @@ in
 
     services.xtreemfs = {
 
-      enable = mkEnableOption "XtreemFS";
+      enable = mkEnableOption (lib.mdDoc "XtreemFS");
 
       homeDir = mkOption {
         type = types.path;
         default = "/var/lib/xtreemfs";
-        description = ''
+        description = lib.mdDoc ''
           XtreemFS home dir for the xtreemfs user.
         '';
       };
@@ -103,7 +103,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable XtreemFS DIR service.
           '';
         };
@@ -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.
@@ -120,7 +120,7 @@ in
         port = mkOption {
           default = 32638;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             The port to listen on for incoming connections (TCP).
           '';
         };
@@ -128,7 +128,7 @@ in
           type = types.str;
           example = "127.0.0.1";
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             If specified, it defines the interface to listen on. If not
             specified, the service will listen on all interfaces (any).
           '';
@@ -136,7 +136,7 @@ in
         httpPort = mkOption {
           default = 30638;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             Specifies the listen port for the HTTP service that returns the
             status page.
           '';
@@ -145,7 +145,7 @@ in
           type = types.enum [ "ASYNC" "SYNC_WRITE_METADATA" "SYNC_WRITE" "FDATASYNC" "FSYNC" ];
           default = "FSYNC";
           example = "FDATASYNC";
-          description = ''
+          description = lib.mdDoc ''
             The sync mode influences how operations are committed to the disk
             log before the operation is acknowledged to the caller.
 
@@ -173,14 +173,14 @@ in
             ssl.trusted_certs.pw = jks_passphrase
             ssl.trusted_certs.container = jks
           '';
-          description = ''
+          description = lib.mdDoc ''
             Configuration of XtreemFS DIR service.
             WARNING: configuration is saved as plaintext inside nix store.
             For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
-          enable = mkEnableOption "XtreemFS DIR replication plugin";
+          enable = mkEnableOption (lib.mdDoc "XtreemFS DIR replication plugin");
           extraConfig = mkOption {
             type = types.lines;
             example = ''
@@ -215,7 +215,7 @@ in
 
               babudb.ssl.authenticationWithoutEncryption = false
             '';
-            description = ''
+            description = lib.mdDoc ''
               Configuration of XtreemFS DIR replication plugin.
               WARNING: configuration is saved as plaintext inside nix store.
               For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
@@ -228,7 +228,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable XtreemFS MRC service.
           '';
         };
@@ -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.
@@ -245,7 +245,7 @@ in
         port = mkOption {
           default = 32636;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             The port to listen on for incoming connections (TCP).
           '';
         };
@@ -253,7 +253,7 @@ in
           example = "127.0.0.1";
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             If specified, it defines the interface to listen on. If not
             specified, the service will listen on all interfaces (any).
           '';
@@ -261,7 +261,7 @@ in
         httpPort = mkOption {
           default = 30636;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             Specifies the listen port for the HTTP service that returns the
             status page.
           '';
@@ -270,7 +270,7 @@ in
           default = "FSYNC";
           type = types.enum [ "ASYNC" "SYNC_WRITE_METADATA" "SYNC_WRITE" "FDATASYNC" "FSYNC" ];
           example = "FDATASYNC";
-          description = ''
+          description = lib.mdDoc ''
             The sync mode influences how operations are committed to the disk
             log before the operation is acknowledged to the caller.
 
@@ -316,14 +316,14 @@ in
             ssl.trusted_certs.pw = jks_passphrase
             ssl.trusted_certs.container = jks
           '';
-          description = ''
+          description = lib.mdDoc ''
             Configuration of XtreemFS MRC service.
             WARNING: configuration is saved as plaintext inside nix store.
             For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
-          enable = mkEnableOption "XtreemFS MRC replication plugin";
+          enable = mkEnableOption (lib.mdDoc "XtreemFS MRC replication plugin");
           extraConfig = mkOption {
             type = types.lines;
             example = ''
@@ -358,7 +358,7 @@ in
 
               babudb.ssl.authenticationWithoutEncryption = false
             '';
-            description = ''
+            description = lib.mdDoc ''
               Configuration of XtreemFS MRC replication plugin.
               WARNING: configuration is saved as plaintext inside nix store.
               For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
@@ -371,7 +371,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable XtreemFS OSD service.
           '';
         };
@@ -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.
@@ -388,7 +388,7 @@ in
         port = mkOption {
           default = 32640;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             The port to listen on for incoming connections (TCP and UDP).
           '';
         };
@@ -396,7 +396,7 @@ in
           example = "127.0.0.1";
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             If specified, it defines the interface to listen on. If not
             specified, the service will listen on all interfaces (any).
           '';
@@ -404,7 +404,7 @@ in
         httpPort = mkOption {
           default = 30640;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             Specifies the listen port for the HTTP service that returns the
             status page.
           '';
@@ -435,7 +435,7 @@ in
             ssl.trusted_certs.pw = jks_passphrase
             ssl.trusted_certs.container = jks
           '';
-          description = ''
+          description = lib.mdDoc ''
             Configuration of XtreemFS OSD service.
             WARNING: configuration is saved as plaintext inside nix store.
             For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
diff --git a/nixos/modules/services/network-filesystems/yandex-disk.nix b/nixos/modules/services/network-filesystems/yandex-disk.nix
index a5b1f9d4ab63..1078df0bed25 100644
--- a/nixos/modules/services/network-filesystems/yandex-disk.nix
+++ b/nixos/modules/services/network-filesystems/yandex-disk.nix
@@ -23,15 +23,15 @@ 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 {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Your yandex.com login name.
         '';
       };
@@ -39,7 +39,7 @@ in
       password = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Your yandex.com password. Warning: it will be world-readable in /nix/store.
         '';
       };
@@ -47,7 +47,7 @@ in
       user = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           The user the yandex-disk daemon should run as.
         '';
       };
@@ -55,14 +55,14 @@ in
       directory = mkOption {
         type = types.path;
         default = "/home/Yandex.Disk";
-        description = "The directory to use for Yandex.Disk storage";
+        description = lib.mdDoc "The directory to use for Yandex.Disk storage";
       };
 
       excludes = mkOption {
         default = "";
         type = types.commas;
         example = "data,backup";
-        description = ''
+        description = lib.mdDoc ''
           Comma-separated list of directories which are excluded from synchronization.
         '';
       };
diff --git a/nixos/modules/services/networking/3proxy.nix b/nixos/modules/services/networking/3proxy.nix
index 326a8671fcca..ef695a7f49fa 100644
--- a/nixos/modules/services/networking/3proxy.nix
+++ b/nixos/modules/services/networking/3proxy.nix
@@ -6,11 +6,11 @@ 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";
-      description = ''
+      description = lib.mdDoc ''
         Ignore all other 3proxy options and load configuration from this file.
       '';
     };
@@ -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,42 +55,24 @@ 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 {
             type = types.str;
             default = "[::]";
             example = "127.0.0.1";
-            description = ''
+            description = lib.mdDoc ''
               Address used for service.
             '';
           };
@@ -98,7 +80,7 @@ in {
             type = types.nullOr types.int;
             default = null;
             example = 3128;
-            description = ''
+            description = lib.mdDoc ''
               Override default port used for service.
             '';
           };
@@ -106,31 +88,23 @@ in {
             type = types.int;
             default = 100;
             example = 1000;
-            description = ''
+            description = lib.mdDoc ''
               Maximum number of simulationeous connections to this service.
             '';
           };
           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,24 +128,18 @@ 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 {
                   type = types.listOf types.str;
                   default = [ ];
                   example = [ "user1" "user2" "user3" ];
-                  description = ''
+                  description = lib.mdDoc ''
                     List of users, use empty list for any.
                   '';
                 };
@@ -179,7 +147,7 @@ in {
                   type = types.listOf types.str;
                   default = [ ];
                   example = [ "127.0.0.1" "192.168.1.0/24" ];
-                  description = ''
+                  description = lib.mdDoc ''
                     List of source IP range, use empty list for any.
                   '';
                 };
@@ -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.
                   '';
                 };
@@ -198,7 +166,7 @@ in {
                   type = types.listOf types.int;
                   default = [ ];
                   example = [ 80 443 ];
-                  description = ''
+                  description = lib.mdDoc ''
                     List of target ports, use empty list for any.
                   '';
                 };
@@ -220,7 +188,7 @@ in {
                 }
               ]
             '';
-            description = ''
+            description = lib.mdDoc ''
               Use this option to limit user access to resources.
             '';
           };
@@ -228,17 +196,17 @@ in {
             type = types.nullOr types.str;
             default = null;
             example = "-46";
-            description = ''
+            description = lib.mdDoc ''
               Extra arguments for service.
-              Consult "Options" section in <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available arguments.
+              Consult "Options" section in [documentation](https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg) for available arguments.
             '';
           };
           extraConfig = mkOption {
             type = types.nullOr types.lines;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Extra configuration for service. Use this to configure things like bandwidth limiter or ACL-based redirection.
-              Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options.
+              Consult [documentation](https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg) for available options.
             '';
           };
         };
@@ -266,14 +234,14 @@ in {
           }
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Use this option to define 3proxy services.
       '';
     };
     denyPrivate = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to deny access to private IP ranges including loopback.
       '';
     };
@@ -290,7 +258,7 @@ in {
         "::1"
         "fc00::/7"
       ];
-      description = ''
+      description = lib.mdDoc ''
         What IP ranges to deny access when denyPrivate is set tu true.
       '';
     };
@@ -301,7 +269,7 @@ in {
             type = types.listOf types.str;
             default = [ ];
             example = [ "127.0.0.53" "192.168.1.3:5353/tcp" ];
-            description = ''
+            description = lib.mdDoc ''
               List of nameservers to use.
 
               Up to 5 nservers may be specified. If no nserver is configured,
@@ -311,12 +279,12 @@ in {
           nscache = mkOption {
             type = types.int;
             default = 65535;
-            description = "Set name cache size for IPv4.";
+            description = lib.mdDoc "Set name cache size for IPv4.";
           };
           nscache6 = mkOption {
             type = types.int;
             default = 65535;
-            description = "Set name cache size for IPv6.";
+            description = lib.mdDoc "Set name cache size for IPv6.";
           };
           nsrecord = mkOption {
             type = types.attrsOf types.str;
@@ -327,21 +295,21 @@ in {
                 "site.local" = "192.168.1.43";
               }
             '';
-            description = "Adds static nsrecords.";
+            description = lib.mdDoc "Adds static nsrecords.";
           };
         };
       };
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Use this option to configure name resolution and DNS caching.
       '';
     };
     extraConfig = mkOption {
       type = types.nullOr types.lines;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Extra configuration, appended to the 3proxy configuration file.
-        Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options.
+        Consult [documentation](https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg) for available options.
       '';
     };
   };
diff --git a/nixos/modules/services/networking/adguardhome.nix b/nixos/modules/services/networking/adguardhome.nix
index 98ddf0716087..bda99cb7942b 100644
--- a/nixos/modules/services/networking/adguardhome.nix
+++ b/nixos/modules/services/networking/adguardhome.nix
@@ -12,41 +12,30 @@ 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 = ''
-        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 = ''
-        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;
       type = bool;
-      description = ''
+      description = lib.mdDoc ''
         Open ports in the firewall for the AdGuard Home web interface. Does not
         open the port needed to access the DNS resolver.
       '';
@@ -55,32 +44,63 @@ in {
     mutableSettings = mkOption {
       default = true;
       type = bool;
-      description = ''
+      description = lib.mdDoc ''
         Allow changes made on the AdGuard Home web interface to persist between
         service restarts.
       '';
     };
 
     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`.
+        :::
       '';
     };
 
     extraArgs = mkOption {
       default = [ ];
       type = listOf str;
-      description = ''
+      description = lib.mdDoc ''
         Extra command line parameters to be passed to the adguardhome binary.
       '';
     };
@@ -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/nixos/modules/services/networking/amuled.nix b/nixos/modules/services/networking/amuled.nix
index aa72a047526b..1cd543358196 100644
--- a/nixos/modules/services/networking/amuled.nix
+++ b/nixos/modules/services/networking/amuled.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run the AMule daemon. You need to manually run "amuled --ec-config" to configure the service for the first time.
         '';
       };
@@ -30,7 +30,7 @@ in
         defaultText = literalExpression ''
           "/home/''${config.${opt.user}}/"
         '';
-        description = ''
+        description = lib.mdDoc ''
           The directory holding configuration, incoming and temporary files.
         '';
       };
@@ -38,7 +38,7 @@ in
       user = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The user the AMule daemon should run as.
         '';
       };
diff --git a/nixos/modules/services/networking/antennas.nix b/nixos/modules/services/networking/antennas.nix
index ef98af22f20f..c0e56890864a 100644
--- a/nixos/modules/services/networking/antennas.nix
+++ b/nixos/modules/services/networking/antennas.nix
@@ -8,30 +8,30 @@ in
 {
   options = {
     services.antennas = {
-      enable = mkEnableOption "Antennas";
+      enable = mkEnableOption (lib.mdDoc "Antennas");
 
       tvheadendUrl = mkOption {
         type        = types.str;
         default     = "http://localhost:9981";
-        description = "URL of Tvheadend.";
+        description = lib.mdDoc "URL of Tvheadend.";
       };
 
       antennasUrl = mkOption {
         type        = types.str;
         default     = "http://127.0.0.1:5004";
-        description = "URL of Antennas.";
+        description = lib.mdDoc "URL of Antennas.";
       };
 
       tunerCount = mkOption {
         type        = types.int;
         default     = 6;
-        description = "Numbers of tuners in tvheadend.";
+        description = lib.mdDoc "Numbers of tuners in tvheadend.";
       };
 
       deviceUUID = mkOption {
         type        = types.str;
         default     = "2f70c0d7-90a3-4429-8275-cbeeee9cd605";
-        description = "Device tuner UUID. Change this if you are running multiple instances.";
+        description = lib.mdDoc "Device tuner UUID. Change this if you are running multiple instances.";
       };
     };
   };
diff --git a/nixos/modules/services/networking/aria2.nix b/nixos/modules/services/networking/aria2.nix
index 156fef144791..e848869cc0ac 100644
--- a/nixos/modules/services/networking/aria2.nix
+++ b/nixos/modules/services/networking/aria2.nix
@@ -28,7 +28,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether or not to enable the headless Aria2 daemon service.
 
           Aria2 daemon can be controlled via the RPC interface using
@@ -41,7 +41,7 @@ in
       openPorts = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open listen and RPC ports found in listenPortRange and rpcListenPort
           options in the firewall.
         '';
@@ -49,26 +49,26 @@ in
       downloadDir = mkOption {
         type = types.path;
         default = downloadDir;
-        description = ''
+        description = lib.mdDoc ''
           Directory to store downloaded files.
         '';
       };
       listenPortRange = mkOption {
         type = types.listOf types.attrs;
         default = [ { from = 6881; to = 6999; } ];
-        description = ''
+        description = lib.mdDoc ''
           Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker.
         '';
       };
       rpcListenPort = mkOption {
         type = types.int;
         default = 6800;
-        description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
+        description = lib.mdDoc "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
       };
       rpcSecret = mkOption {
         type = types.str;
         default = "aria2rpc";
-        description = ''
+        description = lib.mdDoc ''
           Set RPC secret authorization token.
           Read https://aria2.github.io/manual/en/html/aria2c.html#rpc-auth to know how this option value is used.
         '';
@@ -77,7 +77,7 @@ in
         type = types.separatedString " ";
         example = "--rpc-listen-all --remote-time=true";
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Additional arguments to be passed to Aria2.
         '';
       };
diff --git a/nixos/modules/services/networking/asterisk.nix b/nixos/modules/services/networking/asterisk.nix
index af091d55c01b..5a1d03f07211 100644
--- a/nixos/modules/services/networking/asterisk.nix
+++ b/nixos/modules/services/networking/asterisk.nix
@@ -14,28 +14,9 @@ let
 
   # Add filecontents from files of useTheseDefaultConfFiles to confFiles, do not override
   defaultConfFiles = subtractLists (attrNames cfg.confFiles) cfg.useTheseDefaultConfFiles;
-  allConfFiles =
-    cfg.confFiles //
-    builtins.listToAttrs (map (x: { name = x;
-                                    value = builtins.readFile (cfg.package + "/etc/asterisk/" + x); })
-                              defaultConfFiles);
-
-  asteriskEtc = pkgs.stdenv.mkDerivation
-  ((mapAttrs' (name: value: nameValuePair
-        # Fudge the names to make bash happy
-        ((replaceChars ["."] ["_"] name) + "_")
-        (value)
-      ) allConfFiles) //
-  {
-    confFilesString = concatStringsSep " " (
-      attrNames allConfFiles
-    );
-
-    name = "asterisk-etc";
-
+  allConfFiles = {
     # Default asterisk.conf file
-    # (Notice that astetcdir will be set to the path of this derivation)
-    asteriskConf = ''
+    "asterisk.conf".text = ''
       [directories]
       astetcdir => /etc/asterisk
       astmoddir => ${cfg.package}/lib/asterisk/modules
@@ -48,43 +29,28 @@ let
       astrundir => /run/asterisk
       astlogdir => /var/log/asterisk
       astsbindir => ${cfg.package}/sbin
+      ${cfg.extraConfig}
     '';
-    extraConf = cfg.extraConfig;
 
     # Loading all modules by default is considered sensible by the authors of
     # "Asterisk: The Definitive Guide". Secure sites will likely want to
     # specify their own "modules.conf" in the confFiles option.
-    modulesConf = ''
+    "modules.conf".text = ''
       [modules]
       autoload=yes
     '';
 
     # Use syslog for logging so logs can be viewed with journalctl
-    loggerConf = ''
+    "logger.conf".text = ''
       [general]
 
       [logfiles]
       syslog.local0 => notice,warning,error
     '';
+  } //
+    mapAttrs (name: text: { inherit text; }) cfg.confFiles //
+    listToAttrs (map (x: nameValuePair x { source = cfg.package + "/etc/asterisk/" + x; }) defaultConfFiles);
 
-    buildCommand = ''
-      mkdir -p "$out"
-
-      # Create asterisk.conf, pointing astetcdir to the path of this derivation
-      echo "$asteriskConf" | sed "s|@out@|$out|g" > "$out"/asterisk.conf
-      echo "$extraConf" >> "$out"/asterisk.conf
-
-      echo "$modulesConf" > "$out"/modules.conf
-
-      echo "$loggerConf" > "$out"/logger.conf
-
-      # Config files specified in confFiles option override all other files
-      for i in $confFilesString; do
-        conf=$(echo "$i"_ | sed 's/\./_/g')
-        echo "''${!conf}" > "$out"/"$i"
-      done
-    '';
-  });
 in
 
 {
@@ -93,7 +59,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Asterisk PBX server.
         '';
       };
@@ -106,9 +72,9 @@ in
           verbose=3
           debug=3
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options appended to the default
-          <literal>asterisk.conf</literal> file.
+          `asterisk.conf` file.
         '';
       };
 
@@ -161,19 +127,19 @@ in
               ''';
             }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Sets the content of config files (typically ending with
-          <literal>.conf</literal>) in the Asterisk configuration directory.
+          `.conf`) in the Asterisk configuration directory.
 
-          Note that if you want to change <literal>asterisk.conf</literal>, it
-          is preferable to use the <option>services.asterisk.extraConfig</option>
-          option over this option. If <literal>"asterisk.conf"</literal> is
-          specified with the <option>confFiles</option> option (not recommended),
-          you must be prepared to set your own <literal>astetcdir</literal>
+          Note that if you want to change `asterisk.conf`, it
+          is preferable to use the {option}`services.asterisk.extraConfig`
+          option over this option. If `"asterisk.conf"` is
+          specified with the {option}`confFiles` option (not recommended),
+          you must be prepared to set your own `astetcdir`
           path.
 
           See
-          <link xlink:href="http://www.asterisk.org/community/documentation"/>
+          <http://www.asterisk.org/community/documentation>
           for more examples of what is possible here.
         '';
       };
@@ -182,9 +148,9 @@ in
         default = [ "ari.conf" "acl.conf" "agents.conf" "amd.conf" "calendar.conf" "cdr.conf" "cdr_syslog.conf" "cdr_custom.conf" "cel.conf" "cel_custom.conf" "cli_aliases.conf" "confbridge.conf" "dundi.conf" "features.conf" "hep.conf" "iax.conf" "pjsip.conf" "pjsip_wizard.conf" "phone.conf" "phoneprov.conf" "queues.conf" "res_config_sqlite3.conf" "res_parking.conf" "statsd.conf" "udptl.conf" "unistim.conf" ];
         type = types.listOf types.str;
         example = [ "sip.conf" "dundi.conf" ];
-        description = ''Sets these config files to the default content. The default value for
+        description = lib.mdDoc ''Sets these config files to the default content. The default value for
           this option contains all necesscary files to avoid errors at startup.
-          This does not override settings via <option>services.asterisk.confFiles</option>.
+          This does not override settings via {option}`services.asterisk.confFiles`.
         '';
       };
 
@@ -193,7 +159,7 @@ in
         type = types.listOf types.str;
         example =
           [ "-vvvddd" "-e" "1024" ];
-        description = ''
+        description = lib.mdDoc ''
           Additional command line arguments to pass to Asterisk.
         '';
       };
@@ -201,7 +167,7 @@ in
         type = types.package;
         default = pkgs.asterisk;
         defaultText = literalExpression "pkgs.asterisk";
-        description = "The Asterisk package to use.";
+        description = lib.mdDoc "The Asterisk package to use.";
       };
     };
   };
@@ -209,7 +175,9 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = [ cfg.package ];
 
-    environment.etc.asterisk.source = asteriskEtc;
+    environment.etc = mapAttrs' (name: value:
+      nameValuePair "asterisk/${name}" value
+    ) allConfFiles;
 
     users.users.asterisk =
       { name = asteriskUser;
diff --git a/nixos/modules/services/networking/atftpd.nix b/nixos/modules/services/networking/atftpd.nix
index da5e305201f8..e31b447e6c5b 100644
--- a/nixos/modules/services/networking/atftpd.nix
+++ b/nixos/modules/services/networking/atftpd.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the atftpd TFTP server. By default, the server
           binds to address 0.0.0.0.
         '';
@@ -33,7 +33,7 @@ in
             "--verbose=7"
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra command line arguments to pass to atftp.
         '';
       };
@@ -41,7 +41,7 @@ in
       root = mkOption {
         default = "/srv/tftp";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Document root directory for the atftpd.
         '';
       };
diff --git a/nixos/modules/services/networking/autossh.nix b/nixos/modules/services/networking/autossh.nix
index 245f2bfc2cf3..ed9c07d9a147 100644
--- a/nixos/modules/services/networking/autossh.nix
+++ b/nixos/modules/services/networking/autossh.nix
@@ -22,18 +22,18 @@ in
             name = mkOption {
               type = types.str;
               example = "socks-peer";
-              description = "Name of the local AutoSSH session";
+              description = lib.mdDoc "Name of the local AutoSSH session";
             };
             user = mkOption {
               type = types.str;
               example = "bill";
-              description = "Name of the user the AutoSSH session should run as";
+              description = lib.mdDoc "Name of the user the AutoSSH session should run as";
             };
             monitoringPort = mkOption {
               type = types.int;
               default = 0;
               example = 20000;
-              description = ''
+              description = lib.mdDoc ''
                 Port to be used by AutoSSH for peer monitoring. Note, that
                 AutoSSH also uses mport+1. Value of 0 disables the keep-alive
                 style monitoring
@@ -42,7 +42,7 @@ in
             extraArguments = mkOption {
               type = types.separatedString " ";
               example = "-N -D4343 bill@socks.example.net";
-              description = ''
+              description = lib.mdDoc ''
                 Arguments to be passed to AutoSSH and retransmitted to SSH
                 process. Some meaningful options include -N (don't run remote
                 command), -D (open SOCKS proxy on local port), -R (forward
@@ -54,7 +54,7 @@ in
         });
 
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of AutoSSH sessions to start as systemd services. Each service is
           named 'autossh-{session.name}'.
         '';
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 50c4ffdedce8..3933ed5a2315 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -43,7 +43,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         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
@@ -55,16 +55,16 @@ in
       type = types.str;
       default = config.networking.hostName;
       defaultText = literalExpression "config.networking.hostName";
-      description = ''
+      description = lib.mdDoc ''
         Host name advertised on the LAN. If not set, avahi will use the value
-        of <option>config.networking.hostName</option>.
+        of {option}`config.networking.hostName`.
       '';
     };
 
     domainName = mkOption {
       type = types.str;
       default = "local";
-      description = ''
+      description = lib.mdDoc ''
         Domain name for all advertisements.
       '';
     };
@@ -73,7 +73,7 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "0pointer.de" "zeroconf.org" ];
-      description = ''
+      description = lib.mdDoc ''
         List of non-local DNS domains to be browsed.
       '';
     };
@@ -81,22 +81,22 @@ in
     ipv4 = mkOption {
       type = types.bool;
       default = true;
-      description = "Whether to use IPv4.";
+      description = lib.mdDoc "Whether to use IPv4.";
     };
 
     ipv6 = mkOption {
       type = types.bool;
       default = config.networking.enableIPv6;
       defaultText = literalExpression "config.networking.enableIPv6";
-      description = "Whether to use IPv6.";
+      description = lib.mdDoc "Whether to use IPv6.";
     };
 
     interfaces = mkOption {
       type = types.nullOr (types.listOf types.str);
       default = null;
-      description = ''
-        List of network interfaces that should be used by the <command>avahi-daemon</command>.
-        Other interfaces will be ignored. If <literal>null</literal>, all local interfaces
+      description = lib.mdDoc ''
+        List of network interfaces that should be used by the {command}`avahi-daemon`.
+        Other interfaces will be ignored. If `null`, all local interfaces
         except loopback and point-to-point will be used.
       '';
     };
@@ -104,15 +104,16 @@ in
     openFirewall = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      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= ''
+      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.
@@ -122,13 +123,13 @@ in
     wideArea = mkOption {
       type = types.bool;
       default = true;
-      description = "Whether to enable wide-area service discovery.";
+      description = lib.mdDoc "Whether to enable wide-area service discovery.";
     };
 
     reflector = mkOption {
       type = types.bool;
       default = false;
-      description = "Reflect incoming mDNS requests to all allowed network interfaces.";
+      description = lib.mdDoc "Reflect incoming mDNS requests to all allowed network interfaces.";
     };
 
     extraServiceFiles = mkOption {
@@ -150,10 +151,9 @@ in
           ''';
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Specify custom service definitions which are placed in the avahi service directory.
-        See the <citerefentry><refentrytitle>avahi.service</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> manpage for detailed information.
+        See the {manpage}`avahi.service(5)` manpage for detailed information.
       '';
     };
 
@@ -161,25 +161,25 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to allow publishing in general.";
+        description = lib.mdDoc "Whether to allow publishing in general.";
       };
 
       userServices = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to publish user services. Will set <literal>addresses=true</literal>.";
+        description = lib.mdDoc "Whether to publish user services. Will set `addresses=true`.";
       };
 
       addresses = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to register mDNS address records for all local IP addresses.";
+        description = lib.mdDoc "Whether to register mDNS address records for all local IP addresses.";
       };
 
       hinfo = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to register a mDNS HINFO record which contains information about the
           local operating system and CPU.
         '';
@@ -188,7 +188,7 @@ in
       workstation = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to register a service of type "_workstation._tcp" on the local LAN.
         '';
       };
@@ -196,14 +196,14 @@ in
       domain = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to announce the locally used domain name for browsing by other hosts.";
+        description = lib.mdDoc "Whether to announce the locally used domain name for browsing by other hosts.";
       };
     };
 
     nssmdns = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the mDNS NSS (Name Service Switch) plug-in.
         Enabling it allows applications to resolve names in the `.local'
         domain by transparently querying the Avahi daemon.
@@ -213,7 +213,7 @@ in
     cacheEntriesMax = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Number of resource records to be cached per interface. Use 0 to
         disable caching. Avahi daemon defaults to 4096 if not set.
       '';
@@ -222,7 +222,7 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra config to append to avahi-daemon.conf.
       '';
     };
diff --git a/nixos/modules/services/networking/babeld.nix b/nixos/modules/services/networking/babeld.nix
index aae6f1498a42..ff1ac6998ee9 100644
--- a/nixos/modules/services/networking/babeld.nix
+++ b/nixos/modules/services/networking/babeld.nix
@@ -40,13 +40,13 @@ in
 
     services.babeld = {
 
-      enable = mkEnableOption "the babeld network routing daemon";
+      enable = mkEnableOption (lib.mdDoc "the babeld network routing daemon");
 
       interfaceDefaults = mkOption {
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           A set describing default parameters for babeld interfaces.
-          See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
+          See {manpage}`babeld(8)` for options.
         '';
         type = types.nullOr (types.attrsOf types.unspecified);
         example =
@@ -58,9 +58,9 @@ in
 
       interfaces = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           A set describing babeld interfaces.
-          See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
+          See {manpage}`babeld(8)` for options.
         '';
         type = types.attrsOf (types.attrsOf types.unspecified);
         example =
@@ -75,9 +75,9 @@ in
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Options that will be copied to babeld.conf.
-          See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for details.
+          See {manpage}`babeld(8)` for details.
         '';
       };
     };
diff --git a/nixos/modules/services/networking/bee-clef.nix b/nixos/modules/services/networking/bee-clef.nix
index 719714b28982..75e76f019a71 100644
--- a/nixos/modules/services/networking/bee-clef.nix
+++ b/nixos/modules/services/networking/bee-clef.nix
@@ -14,12 +14,12 @@ 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;
         default = "/var/lib/bee-clef";
-        description = ''
+        description = lib.mdDoc ''
           Data dir for bee-clef. Beware that some helper scripts may not work when changed!
           The service itself should work fine, though.
         '';
@@ -28,13 +28,13 @@ in {
       passwordFile = mkOption {
         type = types.nullOr types.str;
         default = "/var/lib/bee-clef/password";
-        description = "Password file for bee-clef.";
+        description = lib.mdDoc "Password file for bee-clef.";
       };
 
       user = mkOption {
         type = types.str;
         default = "bee-clef";
-        description = ''
+        description = lib.mdDoc ''
           User the bee-clef daemon should execute under.
         '';
       };
@@ -42,7 +42,7 @@ in {
       group = mkOption {
         type = types.str;
         default = "bee-clef";
-        description = ''
+        description = lib.mdDoc ''
           Group the bee-clef daemon should execute under.
         '';
       };
diff --git a/nixos/modules/services/networking/bee.nix b/nixos/modules/services/networking/bee.nix
index d6efade0630f..add9861ebfcd 100644
--- a/nixos/modules/services/networking/bee.nix
+++ b/nixos/modules/services/networking/bee.nix
@@ -15,21 +15,21 @@ in {
 
   options = {
     services.bee = {
-      enable = mkEnableOption "Ethereum Swarm Bee";
+      enable = mkEnableOption (lib.mdDoc "Ethereum Swarm Bee");
 
       package = mkOption {
         type = types.package;
         default = pkgs.bee;
         defaultText = literalExpression "pkgs.bee";
         example = literalExpression "pkgs.bee-unstable";
-        description = "The package providing the bee binary for the service.";
+        description = lib.mdDoc "The package providing the bee binary for the service.";
       };
 
       settings = mkOption {
         type = format.type;
-        description = ''
+        description = lib.mdDoc ''
           Ethereum Swarm Bee configuration. Refer to
-          <link xlink:href="https://gateway.ethswarm.org/bzz/docs.swarm.eth/docs/installation/configuration/"/>
+          <https://gateway.ethswarm.org/bzz/docs.swarm.eth/docs/installation/configuration/>
           for details on supported values.
         '';
       };
@@ -37,7 +37,7 @@ in {
       daemonNiceLevel = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Daemon process priority for bee.
           0 is the default Unix process priority, 19 is the lowest.
         '';
@@ -46,7 +46,7 @@ in {
       user = mkOption {
         type = types.str;
         default = "bee";
-        description = ''
+        description = lib.mdDoc ''
           User the bee binary should execute under.
         '';
       };
@@ -54,7 +54,7 @@ in {
       group = mkOption {
         type = types.str;
         default = "bee";
-        description = ''
+        description = lib.mdDoc ''
           Group the bee binary should execute under.
         '';
       };
diff --git a/nixos/modules/services/networking/biboumi.nix b/nixos/modules/services/networking/biboumi.nix
index 3f46b95eaf0c..1428856764e6 100644
--- a/nixos/modules/services/networking/biboumi.nix
+++ b/nixos/modules/services/networking/biboumi.nix
@@ -16,11 +16,11 @@ 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 = ''
-          See <link xlink:href="https://lab.louiz.org/louiz/biboumi/blob/8.5/doc/biboumi.1.rst">biboumi 8.5</link>
+        description = lib.mdDoc ''
+          See [biboumi 8.5](https://lab.louiz.org/louiz/biboumi/blob/8.5/doc/biboumi.1.rst)
           for documentation.
         '';
         default = {};
@@ -34,7 +34,7 @@ in
             default = [];
             example = ["admin@example.org"];
             apply = concatStringsSep ":";
-            description = ''
+            description = lib.mdDoc ''
               The bare JID of the gateway administrator. This JID will have more
               privileges than other standard users, for example some administration
               ad-hoc commands will only be available to that JID.
@@ -43,15 +43,15 @@ in
           options.ca_file = mkOption {
             type = types.path;
             default = "/etc/ssl/certs/ca-certificates.crt";
-            description = ''
+            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 {
             type = with types; either path str;
             default = "${stateDir}/biboumi.sqlite";
-            description = ''
+            description = lib.mdDoc ''
               The name of the database to use.
             '';
             example = "postgresql://user:secret@localhost";
@@ -59,7 +59,7 @@ in
           options.hostname = mkOption {
             type = types.str;
             example = "biboumi.example.org";
-            description = ''
+            description = lib.mdDoc ''
               The hostname served by the XMPP gateway.
               This domain must be configured in the XMPP server
               as an external component.
@@ -69,34 +69,34 @@ in
             type = types.port;
             default = 113;
             example = 0;
-            description = ''
+            description = lib.mdDoc ''
               The TCP port on which to listen for identd queries.
             '';
           };
           options.log_level = mkOption {
             type = types.ints.between 0 3;
             default = 1;
-            description = ''
+            description = lib.mdDoc ''
               Indicate what type of log messages to write in the logs.
               0 is debug, 1 is info, 2 is warning, 3 is error.
             '';
           };
           options.password = mkOption {
             type = with types; nullOr str;
-            description = ''
+            description = lib.mdDoc ''
               The password used to authenticate the XMPP component to your XMPP server.
               This password must be configured in the XMPP server,
               associated with the external component on
-              <link linkend="opt-services.biboumi.settings.hostname">hostname</link>.
+              [hostname](#opt-services.biboumi.settings.hostname).
 
-              Set it to null and use <link linkend="opt-services.biboumi.credentialsFile">credentialsFile</link>
+              Set it to null and use [credentialsFile](#opt-services.biboumi.credentialsFile)
               if you do not want this password to go into the Nix store.
             '';
           };
           options.persistent_by_default = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether all rooms will be persistent by default:
               the value of the “persistent” option in the global configuration of each
               user will be “true”, but the value of each individual room will still
@@ -108,23 +108,23 @@ in
             type = types.path;
             default = "${pkgs.biboumi}/etc/biboumi";
             defaultText = literalExpression ''"''${pkgs.biboumi}/etc/biboumi"'';
-            description = ''
+            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 {
             type = types.port;
             default = 5347;
-            description = ''
+            description = lib.mdDoc ''
               The TCP port to use to connect to the local XMPP component.
             '';
           };
           options.realname_customization = mkOption {
             type = types.bool;
             default = true;
-            description = ''
+            description = lib.mdDoc ''
               Whether the users will be able to use
               the ad-hoc commands that lets them configure
               their realname and username.
@@ -133,7 +133,7 @@ in
           options.realname_from_jid = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether the realname and username of each biboumi
               user will be extracted from their JID.
               Otherwise they will be set to the nick
@@ -143,7 +143,7 @@ in
           options.xmpp_server_ip = mkOption {
             type = types.str;
             default = "127.0.0.1";
-            description = ''
+            description = lib.mdDoc ''
               The IP address to connect to the XMPP server on.
               The connection to the XMPP server is unencrypted,
               so the biboumi instance and the server should
@@ -155,18 +155,18 @@ in
 
       credentialsFile = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to a configuration file to be merged with the settings.
           Beware not to surround "=" with spaces when setting biboumi's options in this file.
           Useful to merge a file which is better kept out of the Nix store
           because it contains sensible data like
-          <link linkend="opt-services.biboumi.settings.password">password</link>.
+          [password](#opt-services.biboumi.settings.password).
         '';
         default = "/dev/null";
         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/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 2045612ec054..f963e341546c 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -17,28 +17,28 @@ let
       name = mkOption {
         type = types.str;
         default = name;
-        description = "Name of the zone.";
+        description = lib.mdDoc "Name of the zone.";
       };
       master = mkOption {
-        description = "Master=false means slave server";
+        description = lib.mdDoc "Master=false means slave server";
         type = types.bool;
       };
       file = mkOption {
         type = types.either types.str types.path;
-        description = "Zone file resource records contain columns of data, separated by whitespace, that define the record.";
+        description = lib.mdDoc "Zone file resource records contain columns of data, separated by whitespace, that define the record.";
       };
       masters = mkOption {
         type = types.listOf types.str;
-        description = "List of servers for inclusion in stub and secondary zones.";
+        description = lib.mdDoc "List of servers for inclusion in stub and secondary zones.";
       };
       slaves = mkOption {
         type = types.listOf types.str;
-        description = "Addresses who may request zone transfers.";
+        description = lib.mdDoc "Addresses who may request zone transfers.";
         default = [ ];
       };
       extraConfig = mkOption {
         type = types.str;
-        description = "Extra zone config to be appended at the end of the zone section.";
+        description = lib.mdDoc "Extra zone config to be appended at the end of the zone section.";
         default = "";
       };
     };
@@ -104,89 +104,89 @@ in
 
     services.bind = {
 
-      enable = mkEnableOption "BIND domain name server";
+      enable = mkEnableOption (lib.mdDoc "BIND domain name server");
 
 
       package = mkOption {
         type = types.package;
         default = pkgs.bind;
         defaultText = literalExpression "pkgs.bind";
-        description = "The BIND package to use.";
+        description = lib.mdDoc "The BIND package to use.";
       };
 
       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.
           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 {
         type = types.str;
         default = "/run/named";
-        description = "Working directory of BIND.";
+        description = lib.mdDoc "Working directory of BIND.";
       };
 
       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,15 +201,15 @@ 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 {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the options section of the
           generated named configuration file.
         '';
@@ -219,10 +219,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/nixos/modules/services/networking/bird-lg.nix b/nixos/modules/services/networking/bird-lg.nix
new file mode 100644
index 000000000000..11cfe3e7ec01
--- /dev/null
+++ b/nixos/modules/services/networking/bird-lg.nix
@@ -0,0 +1,269 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.bird-lg;
+in
+{
+  options = {
+    services.bird-lg = {
+      package = mkOption {
+        type = types.package;
+        default = pkgs.bird-lg;
+        defaultText = literalExpression "pkgs.bird-lg";
+        description = lib.mdDoc "The Bird Looking Glass package to use.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "bird-lg";
+        description = lib.mdDoc "User to run the service.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "bird-lg";
+        description = lib.mdDoc "Group to run the service.";
+      };
+
+      frontend = {
+        enable = mkEnableOption (lib.mdDoc "Bird Looking Glass Frontend Webserver");
+
+        listenAddress = mkOption {
+          type = types.str;
+          default = "127.0.0.1:5000";
+          description = lib.mdDoc "Address to listen on.";
+        };
+
+        proxyPort = mkOption {
+          type = types.port;
+          default = 8000;
+          description = lib.mdDoc "Port bird-lg-proxy is running on.";
+        };
+
+        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.";
+        };
+
+        whois = mkOption {
+          type = types.str;
+          default = "whois.verisign-grs.com";
+          description = lib.mdDoc "Whois server for queries.";
+        };
+
+        dnsInterface = mkOption {
+          type = types.str;
+          default = "asn.cymru.com";
+          description = lib.mdDoc "DNS zone to query ASN information.";
+        };
+
+        bgpMapInfo = mkOption {
+          type = types.listOf types.str;
+          default = [ "asn" "as-name" "ASName" "descr" ];
+          description = lib.mdDoc "Information displayed in bgpmap.";
+        };
+
+        titleBrand = mkOption {
+          type = types.str;
+          default = "Bird-lg Go";
+          description = lib.mdDoc "Prefix of page titles in browser tabs.";
+        };
+
+        netSpecificMode = mkOption {
+          type = types.str;
+          default = "";
+          example = "dn42";
+          description = lib.mdDoc "Apply network-specific changes for some networks.";
+        };
+
+        protocolFilter = mkOption {
+          type = types.listOf types.str;
+          default = [ ];
+          example = [ "ospf" ];
+          description = lib.mdDoc "Information displayed in bgpmap.";
+        };
+
+        nameFilter = mkOption {
+          type = types.str;
+          default = "";
+          example = "^ospf";
+          description = lib.mdDoc "Protocol names to hide in summary tables (RE2 syntax),";
+        };
+
+        timeout = mkOption {
+          type = types.int;
+          default = 120;
+          description = lib.mdDoc "Time before request timed out, in seconds.";
+        };
+
+        navbar = {
+          brand = mkOption {
+            type = types.str;
+            default = "Bird-lg Go";
+            description = lib.mdDoc "Brand to show in the navigation bar .";
+          };
+
+          brandURL = mkOption {
+            type = types.str;
+            default = "/";
+            description = lib.mdDoc "URL of the brand to show in the navigation bar.";
+          };
+
+          allServers = mkOption {
+            type = types.str;
+            default = "ALL Servers";
+            description = lib.mdDoc "Text of 'All server' button in the navigation bar.";
+          };
+
+          allServersURL = mkOption {
+            type = types.str;
+            default = "all";
+            description = lib.mdDoc "URL of 'All servers' button.";
+          };
+        };
+
+        extraArgs = mkOption {
+          type = types.lines;
+          default = "";
+          description = lib.mdDoc ''
+            Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#frontend).
+          '';
+        };
+      };
+
+      proxy = {
+        enable = mkEnableOption (lib.mdDoc "Bird Looking Glass Proxy");
+
+        listenAddress = mkOption {
+          type = types.str;
+          default = "127.0.0.1:8000";
+          description = lib.mdDoc "Address to listen on.";
+        };
+
+        allowedIPs = mkOption {
+          type = types.listOf types.str;
+          default = [ ];
+          example = [ "192.168.25.52" "192.168.25.53" ];
+          description = lib.mdDoc "List of IPs to allow (default all allowed).";
+        };
+
+        birdSocket = mkOption {
+          type = types.str;
+          default = "/run/bird.ctl";
+          example = "/var/run/bird/bird.ctl";
+          description = lib.mdDoc "Bird control socket path.";
+        };
+
+        traceroute = {
+          binary = mkOption {
+            type = types.str;
+            default = "${pkgs.traceroute}/bin/traceroute";
+            defaultText = literalExpression ''"''${pkgs.traceroute}/bin/traceroute"'';
+            description = lib.mdDoc "Traceroute's binary path.";
+          };
+
+          rawOutput = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Display traceroute output in raw format.";
+          };
+        };
+
+        extraArgs = mkOption {
+          type = types.lines;
+          default = "";
+          description = lib.mdDoc ''
+            Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#proxy).
+          '';
+        };
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = {
+    systemd.services = {
+      bird-lg-frontend = mkIf cfg.frontend.enable {
+        enable = true;
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        description = "Bird Looking Glass Frontend Webserver";
+        serviceConfig = {
+          Type = "simple";
+          Restart = "on-failure";
+          ProtectSystem = "full";
+          ProtectHome = "yes";
+          MemoryDenyWriteExecute = "yes";
+          User = cfg.user;
+          Group = cfg.group;
+        };
+        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}
+        '';
+      };
+
+      bird-lg-proxy = mkIf cfg.proxy.enable {
+        enable = true;
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        description = "Bird Looking Glass Proxy";
+        serviceConfig = {
+          Type = "simple";
+          Restart = "on-failure";
+          ProtectSystem = "full";
+          ProtectHome = "yes";
+          MemoryDenyWriteExecute = "yes";
+          User = cfg.user;
+          Group = cfg.group;
+        };
+        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}
+        '';
+      };
+    };
+    users = mkIf (cfg.frontend.enable || cfg.proxy.enable) {
+      groups."bird-lg" = mkIf (cfg.group == "bird-lg") { };
+      users."bird-lg" = mkIf (cfg.user == "bird-lg") {
+        description = "Bird Looking Glass user";
+        extraGroups = lib.optionals (config.services.bird2.enable) [ "bird2" ];
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix
index d409f0602289..77e0b3f8af9b 100644
--- a/nixos/modules/services/networking/bird.nix
+++ b/nixos/modules/services/networking/bird.nix
@@ -10,21 +10,21 @@ 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 = ''
+        description = lib.mdDoc ''
           BIRD Internet Routing Daemon configuration file.
-          <link xlink:href='http://bird.network.cz/'/>
+          <http://bird.network.cz/>
         '';
       };
       checkConfig = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the config should be checked at build time.
           When the config can't be checked during build time, for example when it includes
-          other files, either disable this option or use <code>preCheckConfig</code> to create
+          other files, either disable this option or use `preCheckConfig` to create
           the included files before checking.
         '';
       };
@@ -34,9 +34,9 @@ in
         example = ''
           echo "cost 100;" > include.conf
         '';
-        description = ''
+        description = lib.mdDoc ''
           Commands to execute before the config file check. The file to be checked will be
-          available as <code>bird2.conf</code> in the current directory.
+          available as `bird2.conf` in the current directory.
 
           Files created with this option will not be available at service runtime, only during
           build time checking.
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index 80033d958609..a86d52b7202d 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -11,19 +11,19 @@ let
       name = mkOption {
         type = types.str;
         example = "alice";
-        description = ''
+        description = lib.mdDoc ''
           Username for JSON-RPC connections.
         '';
       };
       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,20 +35,20 @@ let
   bitcoindOpts = { config, lib, name, ...}: {
     options = {
 
-      enable = mkEnableOption "Bitcoin daemon";
+      enable = mkEnableOption (lib.mdDoc "Bitcoin daemon");
 
       package = mkOption {
         type = types.package;
         default = pkgs.bitcoind;
         defaultText = literalExpression "pkgs.bitcoind";
-        description = "The package providing bitcoin binaries.";
+        description = lib.mdDoc "The package providing bitcoin binaries.";
       };
 
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/var/lib/${name}/bitcoin.conf";
-        description = "The configuration file path to supply bitcoind.";
+        description = lib.mdDoc "The configuration file path to supply bitcoind.";
       };
 
       extraConfig = mkOption {
@@ -59,32 +59,32 @@ let
           rpcthreads=16
           logips=1
         '';
-        description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
+        description = lib.mdDoc "Additional configurations to be appended to {file}`bitcoin.conf`.";
       };
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/bitcoind-${name}";
-        description = "The data directory for bitcoind.";
+        description = lib.mdDoc "The data directory for bitcoind.";
       };
 
       user = mkOption {
         type = types.str;
         default = "bitcoind-${name}";
-        description = "The user as which to run bitcoind.";
+        description = lib.mdDoc "The user as which to run bitcoind.";
       };
 
       group = mkOption {
         type = types.str;
         default = config.user;
-        description = "The group as which to run bitcoind.";
+        description = lib.mdDoc "The group as which to run bitcoind.";
       };
 
       rpc = {
         port = mkOption {
           type = types.nullOr types.port;
           default = null;
-          description = "Override the default port on which to listen for JSON-RPC connections.";
+          description = lib.mdDoc "Override the default port on which to listen for JSON-RPC connections.";
         };
         users = mkOption {
           default = {};
@@ -95,33 +95,33 @@ let
             }
           '';
           type = types.attrsOf (types.submodule rpcUserOpts);
-          description = "RPC user information for JSON-RPC connnections.";
+          description = lib.mdDoc "RPC user information for JSON-RPC connections.";
         };
       };
 
       pidFile = mkOption {
         type = types.path;
         default = "${config.dataDir}/bitcoind.pid";
-        description = "Location of bitcoind pid file.";
+        description = lib.mdDoc "Location of bitcoind pid file.";
       };
 
       testnet = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to use the testnet instead of mainnet.";
+        description = lib.mdDoc "Whether to use the testnet instead of mainnet.";
       };
 
       port = mkOption {
         type = types.nullOr types.port;
         default = null;
-        description = "Override the default port on which to listen for connections.";
+        description = lib.mdDoc "Override the default port on which to listen for connections.";
       };
 
       dbCache = mkOption {
         type = types.nullOr (types.ints.between 4 16384);
         default = null;
         example = 4000;
-        description = "Override the default database cache size in MiB.";
+        description = lib.mdDoc "Override the default database cache size in MiB.";
       };
 
       prune = mkOption {
@@ -132,7 +132,7 @@ let
         );
         default = null;
         example = 10000;
-        description = ''
+        description = lib.mdDoc ''
           Reduce storage requirements by enabling pruning (deleting) of old
           blocks. This allows the pruneblockchain RPC to be called to delete
           specific blocks, and enables automatic pruning of old blocks if a
@@ -147,7 +147,7 @@ let
       extraCmdlineOptions = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Extra command line options to pass to bitcoind.
           Run bitcoind --help to list all available options.
         '';
@@ -161,7 +161,7 @@ in
     services.bitcoind = mkOption {
       type = types.attrsOf (types.submodule bitcoindOpts);
       default = {};
-      description = "Specification of one or more bitcoind instances.";
+      description = lib.mdDoc "Specification of one or more bitcoind instances.";
     };
   };
 
@@ -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/nixos/modules/services/networking/bitlbee.nix b/nixos/modules/services/networking/bitlbee.nix
index 8bf04e3a1a23..146bffaa6edf 100644
--- a/nixos/modules/services/networking/bitlbee.nix
+++ b/nixos/modules/services/networking/bitlbee.nix
@@ -49,7 +49,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run the BitlBee IRC to other chat network gateway.
           Running it allows you to access the MSN, Jabber, Yahoo! and ICQ chat
           networks via an IRC client.
@@ -59,17 +59,17 @@ 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;
-        description = ''
+        type = types.port;
+        description = lib.mdDoc ''
           Number of the port BitlBee will be listening to.
         '';
       };
@@ -77,7 +77,7 @@ in
       authBackend = mkOption {
         default = "storage";
         type = types.enum [ "storage" "pam" ];
-        description = ''
+        description = lib.mdDoc ''
           How users are authenticated
             storage -- save passwords internally
             pam -- Linux PAM authentication
@@ -87,7 +87,7 @@ in
       authMode = mkOption {
         default = "Open";
         type = types.enum [ "Open" "Closed" "Registered" ];
-        description = ''
+        description = lib.mdDoc ''
           The following authentication modes are available:
             Open -- Accept connections from anyone, use NickServ for user authentication.
             Closed -- Require authorization (using the PASS command during login) before allowing the user to connect at all.
@@ -98,7 +98,7 @@ in
       hostName = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Normally, BitlBee gets a hostname using getsockname(). If you have a nicer
           alias for your BitlBee daemon, you can set it here and BitlBee will identify
           itself with that name instead.
@@ -109,7 +109,7 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.bitlbee-facebook ]";
-        description = ''
+        description = lib.mdDoc ''
           The list of bitlbee plugins to install.
         '';
       };
@@ -118,7 +118,7 @@ in
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.purple-matrix ]";
-        description = ''
+        description = lib.mdDoc ''
           The list of libpurple plugins to install.
         '';
       };
@@ -126,7 +126,7 @@ in
       configDir = mkOption {
         default = "/var/lib/bitlbee";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Specify an alternative directory to store all the per-user configuration
           files.
         '';
@@ -135,7 +135,7 @@ in
       protocols = mkOption {
         default = "";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           This option allows to remove the support of protocol, even if compiled
           in. If nothing is given, there are no restrictions.
         '';
@@ -144,7 +144,7 @@ in
       extraSettings = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Will be inserted in the Settings section of the config file.
         '';
       };
@@ -152,7 +152,7 @@ in
       extraDefaults = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Will be inserted in the Default section of the config file.
         '';
       };
@@ -174,6 +174,7 @@ in
         serviceConfig = {
           DynamicUser = true;
           StateDirectory = "bitlbee";
+          ReadWritePaths = [ cfg.configDir ];
           ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
         };
       };
diff --git a/nixos/modules/services/networking/blockbook-frontend.nix b/nixos/modules/services/networking/blockbook-frontend.nix
index eeea521c8d51..ab784563e4ac 100644
--- a/nixos/modules/services/networking/blockbook-frontend.nix
+++ b/nixos/modules/services/networking/blockbook-frontend.nix
@@ -10,34 +10,34 @@ let
 
     options = {
 
-      enable = mkEnableOption "blockbook-frontend application.";
+      enable = mkEnableOption (lib.mdDoc "blockbook-frontend application.");
 
       package = mkOption {
         type = types.package;
         default = pkgs.blockbook;
         defaultText = literalExpression "pkgs.blockbook";
-        description = "Which blockbook package to use.";
+        description = lib.mdDoc "Which blockbook package to use.";
       };
 
       user = mkOption {
         type = types.str;
         default = "blockbook-frontend-${name}";
-        description = "The user as which to run blockbook-frontend-${name}.";
+        description = lib.mdDoc "The user as which to run blockbook-frontend-${name}.";
       };
 
       group = mkOption {
         type = types.str;
         default = "${config.user}";
-        description = "The group as which to run blockbook-frontend-${name}.";
+        description = lib.mdDoc "The group as which to run blockbook-frontend-${name}.";
       };
 
       certFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/etc/secrets/blockbook-frontend-${name}/certFile";
-        description = ''
+        description = lib.mdDoc ''
           To enable SSL, specify path to the name of certificate files without extension.
-          Expecting <filename>certFile.crt</filename> and <filename>certFile.key</filename>.
+          Expecting {file}`certFile.crt` and {file}`certFile.key`.
         '';
       };
 
@@ -45,14 +45,14 @@ let
         type = with types; nullOr path;
         default = null;
         example = "${config.dataDir}/config.json";
-        description = "Location of the blockbook configuration file.";
+        description = lib.mdDoc "Location of the blockbook configuration file.";
       };
 
       coinName = mkOption {
         type = types.str;
         default = "Bitcoin";
-        description = ''
-          See <link xlink:href="https://github.com/trezor/blockbook/blob/master/bchain/coins/blockchain.go#L61"/>
+        description = lib.mdDoc ''
+          See <https://github.com/trezor/blockbook/blob/master/bchain/coins/blockchain.go#L61>
           for current of coins supported in master (Note: may differ from release).
         '';
       };
@@ -62,8 +62,8 @@ let
         default = "${config.package}/share/css/";
         defaultText = literalExpression ''"''${package}/share/css/"'';
         example = literalExpression ''"''${dataDir}/static/css/"'';
-        description = ''
-          Location of the dir with <filename>main.css</filename> CSS file.
+        description = lib.mdDoc ''
+          Location of the dir with {file}`main.css` CSS file.
           By default, the one shipped with the package is used.
         '';
       };
@@ -71,68 +71,68 @@ let
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/blockbook-frontend-${name}";
-        description = "Location of blockbook-frontend-${name} data directory.";
+        description = lib.mdDoc "Location of blockbook-frontend-${name} data directory.";
       };
 
       debug = mkOption {
         type = types.bool;
         default = false;
-        description = "Debug mode, return more verbose errors, reload templates on each request.";
+        description = lib.mdDoc "Debug mode, return more verbose errors, reload templates on each request.";
       };
 
       internal = mkOption {
         type = types.nullOr types.str;
         default = ":9030";
-        description = "Internal http server binding <literal>[address]:port</literal>.";
+        description = lib.mdDoc "Internal http server binding `[address]:port`.";
       };
 
       messageQueueBinding = mkOption {
         type = types.str;
         default = "tcp://127.0.0.1:38330";
-        description = "Message Queue Binding <literal>address:port</literal>.";
+        description = lib.mdDoc "Message Queue Binding `address:port`.";
       };
 
       public = mkOption {
         type = types.nullOr types.str;
         default = ":9130";
-        description = "Public http server binding <literal>[address]:port</literal>.";
+        description = lib.mdDoc "Public http server binding `[address]:port`.";
       };
 
       rpc = {
         url = mkOption {
           type = types.str;
           default = "http://127.0.0.1";
-          description = "URL for JSON-RPC connections.";
+          description = lib.mdDoc "URL for JSON-RPC connections.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8030;
-          description = "Port for JSON-RPC connections.";
+          description = lib.mdDoc "Port for JSON-RPC connections.";
         };
 
         user = mkOption {
           type = types.str;
           default = "rpc";
-          description = "Username for JSON-RPC connections.";
+          description = lib.mdDoc "Username for JSON-RPC connections.";
         };
 
         password = mkOption {
           type = types.str;
           default = "rpc";
-          description = ''
+          description = lib.mdDoc ''
             RPC password for JSON-RPC connections.
             Warning: this is stored in cleartext in the Nix store!!!
-            Use <literal>configFile</literal> or <literal>passwordFile</literal> if needed.
+            Use `configFile` or `passwordFile` if needed.
           '';
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             File containing password of the RPC user.
-            Note: This options is ignored when <literal>configFile</literal> is used.
+            Note: This options is ignored when `configFile` is used.
           '';
         };
       };
@@ -140,7 +140,7 @@ let
       sync = mkOption {
         type = types.bool;
         default = true;
-        description = "Synchronizes until tip, if together with zeromq, keeps index synchronized.";
+        description = lib.mdDoc "Synchronizes until tip, if together with zeromq, keeps index synchronized.";
       };
 
       templateDir = mkOption {
@@ -148,7 +148,7 @@ let
         default = "${config.package}/share/templates/";
         defaultText = literalExpression ''"''${package}/share/templates/"'';
         example = literalExpression ''"''${dataDir}/templates/static/"'';
-        description = "Location of the HTML templates. By default, ones shipped with the package are used.";
+        description = lib.mdDoc "Location of the HTML templates. By default, ones shipped with the package are used.";
       };
 
       extraConfig = mkOption {
@@ -171,10 +171,10 @@ let
           "mempool_sub_workers" = 2;
           "block_addresses_to_keep" = 300;
         }'';
-        description = ''
-          Additional configurations to be appended to <filename>coin.conf</filename>.
+        description = lib.mdDoc ''
+          Additional configurations to be appended to {file}`coin.conf`.
           Overrides any already defined configuration options.
-          See <link xlink:href="https://github.com/trezor/blockbook/tree/master/configs/coins"/>
+          See <https://github.com/trezor/blockbook/tree/master/configs/coins>
           for current configuration options supported in master (Note: may differ from release).
         '';
       };
@@ -183,7 +183,7 @@ let
         type = types.listOf types.str;
         default = [];
         example = [ "-workers=1" "-dbcache=0" "-logtosderr" ];
-        description = ''
+        description = lib.mdDoc ''
           Extra command line options to pass to Blockbook.
           Run blockbook --help to list all available options.
         '';
@@ -198,7 +198,7 @@ in
     services.blockbook-frontend = mkOption {
       type = types.attrsOf (types.submodule blockbookOpts);
       default = {};
-      description = "Specification of one or more blockbook-frontend instances.";
+      description = lib.mdDoc "Specification of one or more blockbook-frontend instances.";
     };
   };
 
diff --git a/nixos/modules/services/networking/blocky.nix b/nixos/modules/services/networking/blocky.nix
index 7488e05fc033..971448545616 100644
--- a/nixos/modules/services/networking/blocky.nix
+++ b/nixos/modules/services/networking/blocky.nix
@@ -10,14 +10,14 @@ 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;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Blocky configuration. Refer to
-        <link xlink:href="https://0xerr0r.github.io/blocky/configuration/"/>
+        <https://0xerr0r.github.io/blocky/configuration/>
         for details on supported values.
       '';
     };
diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix
index ff09c0160cb6..168da243dba1 100644
--- a/nixos/modules/services/networking/charybdis.nix
+++ b/nixos/modules/services/networking/charybdis.nix
@@ -18,11 +18,11 @@ in
 
     services.charybdis = {
 
-      enable = mkEnableOption "Charybdis IRC daemon";
+      enable = mkEnableOption (lib.mdDoc "Charybdis IRC daemon");
 
       config = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Charybdis IRC daemon configuration file.
         '';
       };
@@ -30,7 +30,7 @@ in
       statedir = mkOption {
         type = types.path;
         default = "/var/lib/charybdis";
-        description = ''
+        description = lib.mdDoc ''
           Location of the state directory of charybdis.
         '';
       };
@@ -38,7 +38,7 @@ in
       user = mkOption {
         type = types.str;
         default = "ircd";
-        description = ''
+        description = lib.mdDoc ''
           Charybdis IRC daemon user.
         '';
       };
@@ -46,7 +46,7 @@ in
       group = mkOption {
         type = types.str;
         default = "ircd";
-        description = ''
+        description = lib.mdDoc ''
           Charybdis IRC daemon group.
         '';
       };
@@ -54,7 +54,7 @@ in
       motd = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Charybdis MOTD text.
 
           Charybdis will read its MOTD from /etc/charybdis/ircd.motd .
diff --git a/nixos/modules/services/networking/chisel-server.nix b/nixos/modules/services/networking/chisel-server.nix
new file mode 100644
index 000000000000..134c71430cd0
--- /dev/null
+++ b/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/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix
index 0d97d379e907..5a19475161fd 100644
--- a/nixos/modules/services/networking/cjdns.nix
+++ b/nixos/modules/services/networking/cjdns.nix
@@ -13,27 +13,27 @@ let
   { options =
     { password = mkOption {
         type = types.str;
-        description = "Authorized password to the opposite end of the tunnel.";
+        description = lib.mdDoc "Authorized password to the opposite end of the tunnel.";
       };
       login = mkOption {
         default = "";
         type = types.str;
-        description = "(optional) name your peer has for you";
+        description = lib.mdDoc "(optional) name your peer has for you";
       };
       peerName = mkOption {
         default = "";
         type = types.str;
-        description = "(optional) human-readable name for peer";
+        description = lib.mdDoc "(optional) human-readable name for peer";
       };
       publicKey = mkOption {
         type = types.str;
-        description = "Public key at the opposite end of the tunnel.";
+        description = lib.mdDoc "Public key at the opposite end of the tunnel.";
       };
       hostname = mkOption {
         default = "";
         example = "foobar.hype";
         type = types.str;
-        description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
+        description = lib.mdDoc "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
       };
     };
   };
@@ -87,7 +87,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the cjdns network encryption
           and routing engine. A file at /etc/cjdns.keys will
           be created if it does not exist to contain a random
@@ -99,7 +99,7 @@ in
         type = types.attrs;
         default = {};
         example = { router.interface.tunDevice = "tun10"; };
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration, given as attrs, that will be merged recursively
           with the rest of the JSON generated by this module, at the root node.
         '';
@@ -109,7 +109,7 @@ in
         type = types.nullOr types.path;
         default = null;
         example = "/etc/cjdroute.conf";
-        description = ''
+        description = lib.mdDoc ''
           Ignore all other cjdns options and load configuration from this file.
         '';
       };
@@ -122,7 +122,7 @@ in
           "z9md3t4p45mfrjzdjurxn4wuj0d8swv"
           "49275fut6tmzu354pq70sr5b95qq0vj"
         ];
-        description = ''
+        description = lib.mdDoc ''
           Any remote cjdns nodes that offer these passwords on
           connection will be allowed to route through this node.
         '';
@@ -132,7 +132,7 @@ in
         bind = mkOption {
           type = types.str;
           default = "127.0.0.1:11234";
-          description = ''
+          description = lib.mdDoc ''
             Bind the administration port to this address and port.
           '';
         };
@@ -143,7 +143,7 @@ in
           type = types.str;
           default = "";
           example = "192.168.1.32:43211";
-          description = ''
+          description = lib.mdDoc ''
             Address and port to bind UDP tunnels to.
           '';
          };
@@ -159,7 +159,7 @@ in
               };
             }
           '';
-          description = ''
+          description = lib.mdDoc ''
             Credentials for making UDP tunnels.
           '';
         };
@@ -171,16 +171,16 @@ in
           default = "";
           example = "eth0";
           description =
-            ''
+            lib.mdDoc ''
               Bind to this device for native ethernet operation.
-              <literal>all</literal> is a pseudo-name which will try to connect to all devices.
+              `all` is a pseudo-name which will try to connect to all devices.
             '';
         };
 
         beacon = mkOption {
           type = types.int;
           default = 2;
-          description = ''
+          description = lib.mdDoc ''
             Auto-connect to other cjdns nodes on the same network.
             Options:
               0: Disabled.
@@ -206,7 +206,7 @@ in
               };
             }
           '';
-          description = ''
+          description = lib.mdDoc ''
             Credentials for connecting look similar to UDP credientials
             except they begin with the mac address.
           '';
@@ -216,9 +216,9 @@ in
       addExtraHosts = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add cjdns peers with an associated hostname to
-          <filename>/etc/hosts</filename>.  Beware that enabling this
+          {file}`/etc/hosts`.  Beware that enabling this
           incurs heavy eval-time costs.
         '';
       };
diff --git a/nixos/modules/services/networking/cloudflare-dyndns.nix b/nixos/modules/services/networking/cloudflare-dyndns.nix
new file mode 100644
index 000000000000..627fdb880a67
--- /dev/null
+++ b/nixos/modules/services/networking/cloudflare-dyndns.nix
@@ -0,0 +1,93 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cloudflare-dyndns;
+in
+{
+  options = {
+    services.cloudflare-dyndns = {
+      enable = mkEnableOption (lib.mdDoc "Cloudflare Dynamic DNS Client");
+
+      apiTokenFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = lib.mdDoc ''
+          The path to a file containing the CloudFlare API token.
+
+          The file must have the form `CLOUDFLARE_API_TOKEN=...`
+        '';
+      };
+
+      domains = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = lib.mdDoc ''
+          List of domain names to update records for.
+        '';
+      };
+
+      proxied = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether this is a DNS-only record, or also being proxied through CloudFlare.
+        '';
+      };
+
+      ipv4 = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Whether to enable setting IPv4 A records.
+        '';
+      };
+
+      ipv6 = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to enable setting IPv6 AAAA records.
+        '';
+      };
+
+      deleteMissing = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to delete the record when no IP address is found.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.cloudflare-dyndns = {
+      description = "CloudFlare Dynamic DNS Client";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      startAt = "*:0/5";
+
+      environment = {
+        CLOUDFLARE_DOMAINS = toString cfg.domains;
+      };
+
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        StateDirectory = "cloudflare-dyndns";
+        EnvironmentFile = cfg.apiTokenFile;
+        ExecStart =
+          let
+            args = [ "--cache-file /var/lib/cloudflare-dyndns/ip.cache" ]
+              ++ (if cfg.ipv4 then [ "-4" ] else [ "-no-4" ])
+              ++ (if cfg.ipv6 then [ "-6" ] else [ "-no-6" ])
+              ++ optional cfg.deleteMissing "--delete-missing"
+              ++ optional cfg.proxied "--proxied";
+          in
+          "${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/cloudflared.nix b/nixos/modules/services/networking/cloudflared.nix
new file mode 100644
index 000000000000..c8fc9fafee6d
--- /dev/null
+++ b/nixos/modules/services/networking/cloudflared.nix
@@ -0,0 +1,332 @@
+{ 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 = with types; nullOr str;
+            default = null;
+            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 = with types; nullOr str;
+            default = null;
+            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 = lib.mdDoc "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" ];
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              User = cfg.user;
+              Group = cfg.group;
+              ExecStart = "${cfg.package}/bin/cloudflared tunnel --config=${mkConfigFile} --no-autoupdate run";
+              Restart = "always";
+            };
+          })
+        )
+        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/nixos/modules/services/networking/cntlm.nix b/nixos/modules/services/networking/cntlm.nix
index eea28e12ce0e..41510a8f074d 100644
--- a/nixos/modules/services/networking/cntlm.nix
+++ b/nixos/modules/services/networking/cntlm.nix
@@ -33,37 +33,37 @@ 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;
-      description = ''
+      description = lib.mdDoc ''
         Proxy account name, without the possibility to include domain name ('at' sign is interpreted literally).
       '';
     };
 
     domain = mkOption {
       type = types.str;
-      description = "Proxy account domain/workgroup name.";
+      description = lib.mdDoc "Proxy account domain/workgroup name.";
     };
 
     password = mkOption {
       default = "/etc/cntlm.password";
       type = types.str;
-      description = "Proxy account password. Note: use chmod 0600 on /etc/cntlm.password for security.";
+      description = lib.mdDoc "Proxy account password. Note: use chmod 0600 on /etc/cntlm.password for security.";
     };
 
     netbios_hostname = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         The hostname of your machine.
       '';
     };
 
     proxy = mkOption {
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of NTLM/NTLMv2 authenticating HTTP proxies.
 
         Parent proxy, which requires authentication. The same as proxy on the command-line, can be used more than  once  to  specify  unlimited
@@ -74,7 +74,7 @@ in
     };
 
     noproxy = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         A list of domains where the proxy is skipped.
       '';
       default = [];
@@ -85,19 +85,19 @@ in
     port = mkOption {
       default = [3128];
       type = types.listOf types.port;
-      description = "Specifies on which ports the cntlm daemon listens.";
+      description = lib.mdDoc "Specifies on which ports the cntlm daemon listens.";
     };
 
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = "Additional config appended to the end of the generated <filename>cntlm.conf</filename>.";
+      description = lib.mdDoc "Additional config appended to the end of the generated {file}`cntlm.conf`.";
     };
 
     configText = mkOption {
        type = types.lines;
        default = "";
-       description = "Verbatim contents of <filename>cntlm.conf</filename>.";
+       description = lib.mdDoc "Verbatim contents of {file}`cntlm.conf`.";
     };
 
   };
diff --git a/nixos/modules/services/networking/connman.nix b/nixos/modules/services/networking/connman.nix
index 9945dc83a279..498991419579 100644
--- a/nixos/modules/services/networking/connman.nix
+++ b/nixos/modules/services/networking/connman.nix
@@ -27,7 +27,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use ConnMan for managing your network connections.
         '';
       };
@@ -35,7 +35,7 @@ in {
       enableVPN = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable ConnMan VPN service.
         '';
       };
@@ -43,7 +43,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Configuration lines appended to the generated connman configuration file.
         '';
       };
@@ -51,7 +51,7 @@ in {
       networkInterfaceBlacklist = mkOption {
         type = with types; listOf str;
         default = [ "vmnet" "vboxnet" "virbr" "ifb" "ve" ];
-        description = ''
+        description = lib.mdDoc ''
           Default blacklisted interfaces, this includes NixOS containers interfaces (ve).
         '';
       };
@@ -60,9 +60,9 @@ in {
         backend = mkOption {
           type = types.enum [ "wpa_supplicant" "iwd" ];
           default = "wpa_supplicant";
-          description = ''
+          description = lib.mdDoc ''
             Specify the Wi-Fi backend used.
-            Currently supported are <option>wpa_supplicant</option> or <option>iwd</option>.
+            Currently supported are {option}`wpa_supplicant` or {option}`iwd`.
           '';
         };
       };
@@ -71,14 +71,14 @@ in {
         type = with types; listOf str;
         default = [ ];
         example = [ "--nodnsproxy" ];
-        description = ''
+        description = lib.mdDoc ''
           Extra flags to pass to connmand
         '';
       };
 
       package = mkOption {
         type = types.package;
-        description = "The connman package / build flavor";
+        description = lib.mdDoc "The connman package / build flavor";
         default = connman;
         defaultText = literalExpression "pkgs.connman";
         example = literalExpression "pkgs.connmanFull";
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index cb53cc01f52d..f1c36138be3e 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -28,7 +28,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enables the consul daemon.
         '';
       };
@@ -37,7 +37,7 @@ in
         type = types.package;
         default = pkgs.consul;
         defaultText = literalExpression "pkgs.consul";
-        description = ''
+        description = lib.mdDoc ''
           The package used for the Consul agent and CLI.
         '';
       };
@@ -46,7 +46,7 @@ in
       webUi = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enables the web interface on the consul http port.
         '';
       };
@@ -54,7 +54,7 @@ in
       leaveOnStop = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, causes a leave action to be sent when closing consul.
           This allows a clean termination of the node, but permanently removes
           it from the cluster. You probably don't want this option unless you
@@ -68,7 +68,7 @@ in
         advertise = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The name of the interface to pull the advertise_addr from.
           '';
         };
@@ -76,7 +76,7 @@ in
         bind = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The name of the interface to pull the bind_addr from.
           '';
         };
@@ -85,7 +85,7 @@ in
       forceAddrFamily = mkOption {
         type = types.enum [ "any" "ipv4" "ipv6" ];
         default = "any";
-        description = ''
+        description = lib.mdDoc ''
           Whether to bind ipv4/ipv6 or both kind of addresses.
         '';
       };
@@ -93,7 +93,7 @@ in
       forceIpv4 = mkOption {
         type = types.nullOr types.bool;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Deprecated: Use consul.forceAddrFamily instead.
           Whether we should force the interfaces to only pull ipv4 addresses.
         '';
@@ -102,7 +102,7 @@ in
       dropPrivileges = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the consul agent should be run as a non-root consul user.
         '';
       };
@@ -110,7 +110,7 @@ in
       extraConfig = mkOption {
         default = { };
         type = types.attrsOf types.anything;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options which are serialized to json and added
           to the config.json file.
         '';
@@ -119,42 +119,42 @@ in
       extraConfigFiles = mkOption {
         default = [ ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Additional configuration files to pass to consul
           NOTE: These will not trigger the service to be restarted when altered.
         '';
       };
 
       alerts = {
-        enable = mkEnableOption "consul-alerts";
+        enable = mkEnableOption (lib.mdDoc "consul-alerts");
 
         package = mkOption {
-          description = "Package to use for consul-alerts.";
+          description = lib.mdDoc "Package to use for consul-alerts.";
           default = pkgs.consul-alerts;
           defaultText = literalExpression "pkgs.consul-alerts";
           type = types.package;
         };
 
         listenAddr = mkOption {
-          description = "Api listening address.";
+          description = lib.mdDoc "Api listening address.";
           default = "localhost:9000";
           type = types.str;
         };
 
         consulAddr = mkOption {
-          description = "Consul api listening adddress";
+          description = lib.mdDoc "Consul api listening address";
           default = "localhost:8500";
           type = types.str;
         };
 
         watchChecks = mkOption {
-          description = "Whether to enable check watcher.";
+          description = lib.mdDoc "Whether to enable check watcher.";
           default = true;
           type = types.bool;
         };
 
         watchEvents = mkOption {
-          description = "Whether to enable event watcher.";
+          description = lib.mdDoc "Whether to enable event watcher.";
           default = true;
           type = types.bool;
         };
@@ -201,7 +201,7 @@ in
         serviceConfig = {
           ExecStart = "@${cfg.package}/bin/consul 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";
diff --git a/nixos/modules/services/networking/coredns.nix b/nixos/modules/services/networking/coredns.nix
index 88615d8e610f..f928cdf96143 100644
--- a/nixos/modules/services/networking/coredns.nix
+++ b/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 = "";
@@ -17,14 +17,17 @@ in {
         }
       '';
       type = types.lines;
-      description = "Verbatim Corefile to use. See <link xlink:href=\"https://coredns.io/manual/toc/#configuration\"/> for details.";
+      description = lib.mdDoc ''
+        Verbatim Corefile to use.
+        See <https://coredns.io/manual/toc/#configuration> for details.
+      '';
     };
 
     package = mkOption {
       default = pkgs.coredns;
       defaultText = literalExpression "pkgs.coredns";
       type = types.package;
-      description = "Coredns package to use.";
+      description = lib.mdDoc "Coredns package to use.";
     };
   };
 
diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix
index 9d79d5d7686b..0c6fb7a17cab 100644
--- a/nixos/modules/services/networking/corerad.nix
+++ b/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;
@@ -36,8 +36,8 @@ in {
           };
         }
       '';
-      description = ''
-        Configuration for CoreRAD, see <link xlink:href="https://github.com/mdlayher/corerad/blob/main/internal/config/reference.toml"/>
+      description = lib.mdDoc ''
+        Configuration for CoreRAD, see <https://github.com/mdlayher/corerad/blob/main/internal/config/reference.toml>
         for supported values. Ignored if configFile is set.
       '';
     };
@@ -45,14 +45,14 @@ in {
     configFile = mkOption {
       type = types.path;
       example = literalExpression ''"''${pkgs.corerad}/etc/corerad/corerad.toml"'';
-      description = "Path to CoreRAD TOML configuration file.";
+      description = lib.mdDoc "Path to CoreRAD TOML configuration file.";
     };
 
     package = mkOption {
       default = pkgs.corerad;
       defaultText = literalExpression "pkgs.corerad";
       type = types.package;
-      description = "CoreRAD package to use.";
+      description = lib.mdDoc "CoreRAD package to use.";
     };
   };
 
diff --git a/nixos/modules/services/networking/coturn.nix b/nixos/modules/services/networking/coturn.nix
index ce563c31136f..2f34a72377ce 100644
--- a/nixos/modules/services/networking/coturn.nix
+++ b/nixos/modules/services/networking/coturn.nix
@@ -40,11 +40,11 @@ ${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;
-        description = ''
+        description = lib.mdDoc ''
           TURN listener port for UDP and TCP.
           Note: actually, TLS and DTLS sessions can connect to the
           "plain" TCP and UDP port(s), too - if allowed by configuration.
@@ -53,7 +53,7 @@ in {
       tls-listening-port = mkOption {
         type = types.int;
         default = 5349;
-        description = ''
+        description = lib.mdDoc ''
           TURN listener port for TLS.
           Note: actually, "plain" TCP and UDP sessions can connect to the TLS and
           DTLS port(s), too - if allowed by configuration. The TURN server
@@ -69,7 +69,7 @@ in {
         type = types.int;
         default = cfg.listening-port + 1;
         defaultText = literalExpression "listening-port + 1";
-        description = ''
+        description = lib.mdDoc ''
           Alternative listening port for UDP and TCP listeners;
           default (or zero) value means "listening port plus one".
           This is needed for RFC 5780 support
@@ -84,7 +84,7 @@ in {
         type = types.int;
         default = cfg.tls-listening-port + 1;
         defaultText = literalExpression "tls-listening-port + 1";
-        description = ''
+        description = lib.mdDoc ''
           Alternative listening port for TLS and DTLS protocols.
         '';
       };
@@ -92,7 +92,7 @@ in {
         type = types.listOf types.str;
         default = [];
         example = [ "203.0.113.42" "2001:DB8::42" ];
-        description = ''
+        description = lib.mdDoc ''
           Listener IP addresses of relay server.
           If no IP(s) specified in the config file or in the command line options,
           then all IPv4 and IPv6 system IPs will be used for listening.
@@ -102,7 +102,7 @@ in {
         type = types.listOf types.str;
         default = [];
         example = [ "203.0.113.42" "2001:DB8::42" ];
-        description = ''
+        description = lib.mdDoc ''
           Relay address (the local IP address that will be used to relay the
           packets to the peer).
           Multiple relay addresses may be used.
@@ -118,28 +118,28 @@ in {
       min-port = mkOption {
         type = types.int;
         default = 49152;
-        description = ''
+        description = lib.mdDoc ''
           Lower bound of UDP relay endpoints
         '';
       };
       max-port = mkOption {
         type = types.int;
         default = 65535;
-        description = ''
+        description = lib.mdDoc ''
           Upper bound of UDP relay endpoints
         '';
       };
       lt-cred-mech = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Use long-term credential mechanism.
         '';
       };
       no-auth = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This option is opposite to lt-cred-mech.
           (TURN Server with no-auth option allows anonymous access).
           If neither option is defined, and no users are defined,
@@ -151,7 +151,7 @@ in {
       use-auth-secret = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           TURN REST API flag.
           Flag that sets a special authorization option that is based upon authentication secret.
           This feature can be used with the long-term authentication mechanism, only.
@@ -175,7 +175,7 @@ in {
       static-auth-secret = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           'Static' authentication secret value (a string) for TURN REST API only.
           If not set, then the turn server
           will try to use the 'dynamic' value in turn_secret table
@@ -186,7 +186,7 @@ in {
       static-auth-secret-file = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to the file containing the static authentication secret.
         '';
       };
@@ -195,7 +195,7 @@ in {
         default = config.networking.hostName;
         defaultText = literalExpression "config.networking.hostName";
         example = "example.com";
-        description = ''
+        description = lib.mdDoc ''
           The default realm to be used for the users when no explicit
           origin/realm relationship was found in the database, or if the TURN
           server is not using any database (just the commands-line settings
@@ -207,7 +207,7 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "/var/lib/acme/example.com/fullchain.pem";
-        description = ''
+        description = lib.mdDoc ''
           Certificate file in PEM format.
         '';
       };
@@ -215,21 +215,21 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "/var/lib/acme/example.com/key.pem";
-        description = ''
+        description = lib.mdDoc ''
           Private key file in PEM format.
         '';
       };
       dh-file = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Use custom DH TLS key, stored in PEM format in the file.
         '';
       };
       secure-stun = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Require authentication of the STUN Binding request.
           By default, the clients are allowed anonymous access to the STUN Binding functionality.
         '';
@@ -237,28 +237,28 @@ in {
       no-cli = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Turn OFF the CLI support.
         '';
       };
       cli-ip = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           Local system IP address to be used for CLI server endpoint.
         '';
       };
       cli-port = mkOption {
         type = types.int;
         default = 5766;
-        description = ''
+        description = lib.mdDoc ''
           CLI server port.
         '';
       };
       cli-password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           CLI access password.
           For the security reasons, it is recommended to use the encrypted
           for of the password (see the -P command in the turnadmin utility).
@@ -267,37 +267,37 @@ in {
       no-udp = mkOption {
         type = types.bool;
         default = false;
-        description = "Disable UDP client listener";
+        description = lib.mdDoc "Disable UDP client listener";
       };
       no-tcp = mkOption {
         type = types.bool;
         default = false;
-        description = "Disable TCP client listener";
+        description = lib.mdDoc "Disable TCP client listener";
       };
       no-tls = mkOption {
         type = types.bool;
         default = false;
-        description = "Disable TLS client listener";
+        description = lib.mdDoc "Disable TLS client listener";
       };
       no-dtls = mkOption {
         type = types.bool;
         default = false;
-        description = "Disable DTLS client listener";
+        description = lib.mdDoc "Disable DTLS client listener";
       };
       no-udp-relay = mkOption {
         type = types.bool;
         default = false;
-        description = "Disable UDP relay endpoints";
+        description = lib.mdDoc "Disable UDP relay endpoints";
       };
       no-tcp-relay = mkOption {
         type = types.bool;
         default = false;
-        description = "Disable TCP relay endpoints";
+        description = lib.mdDoc "Disable TCP relay endpoints";
       };
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional configuration options";
+        description = lib.mdDoc "Additional configuration options";
       };
     };
   };
@@ -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/nixos/modules/services/networking/create_ap.nix b/nixos/modules/services/networking/create_ap.nix
index a3c330fab007..e772cf21ec57 100644
--- a/nixos/modules/services/networking/create_ap.nix
+++ b/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/nixos/modules/services/networking/croc.nix b/nixos/modules/services/networking/croc.nix
index d044979e10df..45bfd447da45 100644
--- a/nixos/modules/services/networking/croc.nix
+++ b/nixos/modules/services/networking/croc.nix
@@ -6,19 +6,19 @@ 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];
-      description = "Ports of the relay.";
+      description = lib.mdDoc "Ports of the relay.";
     };
     pass = lib.mkOption {
       type = with types; either path str;
       default = "pass123";
-      description = "Password or passwordfile for the relay.";
+      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/nixos/modules/services/networking/dante.nix b/nixos/modules/services/networking/dante.nix
index 20d4faa1cdb1..605f2d74f827 100644
--- a/nixos/modules/services/networking/dante.nix
+++ b/nixos/modules/services/networking/dante.nix
@@ -19,11 +19,11 @@ in
 
   options = {
     services.dante = {
-      enable = mkEnableOption "Dante SOCKS proxy";
+      enable = mkEnableOption (lib.mdDoc "Dante SOCKS proxy");
 
       config = mkOption {
         type        = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Contents of Dante's configuration file.
           NOTE: user.privileged, user.unprivileged and logoutput are set by the service.
         '';
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
index d025c8f8177a..4d843641f58a 100644
--- a/nixos/modules/services/networking/ddclient.nix
+++ b/nixos/modules/services/networking/ddclient.nix
@@ -13,7 +13,7 @@ let
     foreground=YES
     use=${cfg.use}
     login=${cfg.username}
-    password=${lib.optionalString (cfg.protocol == "nsupdate") "/run/${RuntimeDirectory}/ddclient.key"}
+    password=${if cfg.protocol == "nsupdate" then "/run/${RuntimeDirectory}/ddclient.key" else "@password_placeholder@"}
     protocol=${cfg.protocol}
     ${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
     ${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
@@ -33,10 +33,9 @@ let
     ${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then ''
       install ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
     '' else if (cfg.passwordFile != null) then ''
-      password=$(printf "%q" "$(head -n 1 "${cfg.passwordFile}")")
-      sed -i "s|^password=$|password=$password|" /run/${RuntimeDirectory}/ddclient.conf
+      "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf"
     '' else ''
-      sed -i '/^password=$/d' /run/${RuntimeDirectory}/ddclient.conf
+      sed -i '/^password=@password_placeholder@$/d' /run/${RuntimeDirectory}/ddclient.conf
     '')}
   '';
 
@@ -64,7 +63,7 @@ with lib;
       enable = mkOption {
         default = false;
         type = bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
         '';
       };
@@ -72,8 +71,8 @@ with lib;
       package = mkOption {
         type = package;
         default = pkgs.ddclient;
-        defaultText = "pkgs.ddclient";
-        description = ''
+        defaultText = lib.literalExpression "pkgs.ddclient";
+        description = lib.mdDoc ''
           The ddclient executable package run by the service.
         '';
       };
@@ -81,7 +80,7 @@ with lib;
       domains = mkOption {
         default = [ "" ];
         type = listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Domain name(s) to synchronize.
         '';
       };
@@ -91,7 +90,7 @@ with lib;
         default = lib.optionalString (config.services.ddclient.protocol == "nsupdate") "${pkgs.bind.dnsutils}/bin/nsupdate";
         defaultText = "";
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           User name.
         '';
       };
@@ -99,7 +98,7 @@ with lib;
       passwordFile = mkOption {
         default = null;
         type = nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           A file containing the password or a TSIG key in named format when using the nsupdate protocol.
         '';
       };
@@ -107,16 +106,16 @@ with lib;
       interval = mkOption {
         default = "10min";
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           The interval at which to run the check and update.
-          See <command>man 7 systemd.time</command> for the format.
+          See {command}`man 7 systemd.time` for the format.
         '';
       };
 
       configFile = mkOption {
         default = null;
         type = nullOr path;
-        description = ''
+        description = lib.mdDoc ''
           Path to configuration file.
           When set this overrides the generated configuration from module options.
         '';
@@ -126,7 +125,7 @@ with lib;
       protocol = mkOption {
         default = "dyndns2";
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols).
         '';
       };
@@ -134,7 +133,7 @@ with lib;
       server = mkOption {
         default = "";
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           Server address.
         '';
       };
@@ -142,7 +141,7 @@ with lib;
       ssl = mkOption {
         default = true;
         type = bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use SSL/TLS to connect to dynamic DNS provider.
         '';
       };
@@ -150,7 +149,7 @@ with lib;
       ipv6 = mkOption {
         default = false;
         type = bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use IPv6.
         '';
       };
@@ -159,7 +158,7 @@ with lib;
       quiet = mkOption {
         default = false;
         type = bool;
-        description = ''
+        description = lib.mdDoc ''
           Print no messages for unnecessary updates.
         '';
       };
@@ -167,7 +166,7 @@ with lib;
       script = mkOption {
         default = "";
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           script as required by some providers.
         '';
       };
@@ -175,15 +174,15 @@ with lib;
       use = mkOption {
         default = "web, web=checkip.dyndns.com/, web-skip='Current IP Address: '";
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           Method to determine the IP address to send to the dynamic DNS provider.
         '';
       };
 
       verbose = mkOption {
-        default = true;
+        default = false;
         type = bool;
-        description = ''
+        description = lib.mdDoc ''
           Print verbose information.
         '';
       };
@@ -191,7 +190,7 @@ with lib;
       zone = mkOption {
         default = "";
         type = str;
-        description = ''
+        description = lib.mdDoc ''
           zone as required by some providers.
         '';
       };
@@ -199,8 +198,12 @@ with lib;
       extraConfig = mkOption {
         default = "";
         type = lines;
-        description = ''
+        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.
+          :::
         '';
       };
     };
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index 3eb7ca99eafd..ac5d45a65e3b 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -103,7 +103,7 @@ in
     networking.dhcpcd.enable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable dhcpcd for device configuration. This is mainly to
         explicitly disable dhcpcd (for example when using networkd).
       '';
@@ -112,7 +112,7 @@ in
     networking.dhcpcd.persistent = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
           Whenever to leave interfaces configured on dhcpcd daemon
           shutdown. Set to true if you have your root or store mounted
           over the network or this machine accepts SSH connections
@@ -124,7 +124,7 @@ in
     networking.dhcpcd.denyInterfaces = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
          Disable the DHCP client for any interface whose name matches
          any of the shell glob patterns in this list. The purpose of
          this option is to blacklist virtual interfaces such as those
@@ -135,7 +135,7 @@ in
     networking.dhcpcd.allowInterfaces = mkOption {
       type = types.nullOr (types.listOf types.str);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
          Enable the DHCP client for any interface whose name matches
          any of the shell glob patterns in this list. Any interface not
          explicitly matched by this pattern will be denied. This pattern only
@@ -146,7 +146,7 @@ in
     networking.dhcpcd.extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
          Literal string to append to the config file generated for dhcpcd.
       '';
     };
@@ -155,7 +155,7 @@ in
       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.
       '';
@@ -164,7 +164,7 @@ in
     networking.dhcpcd.wait = mkOption {
       type = types.enum [ "background" "any" "ipv4" "ipv6" "both" "if-carrier-up" ];
       default = "any";
-      description = ''
+      description = lib.mdDoc ''
         This option specifies when the dhcpcd service will fork to background.
         If set to "background", dhcpcd will fork to background immediately.
         If set to "ipv4" or "ipv6", dhcpcd will wait for the corresponding IP
@@ -215,7 +215,7 @@ in
         # dhcpcd.  So do a "systemctl restart" instead.
         stopIfChanged = false;
 
-        path = [ dhcpcd pkgs.nettools pkgs.openresolv ];
+        path = [ dhcpcd pkgs.nettools config.networking.resolvconf.package ];
 
         unitConfig.ConditionCapability = "CAP_NET_ADMIN";
 
diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix
index 49950efc0a1b..0bd5e4ef5535 100644
--- a/nixos/modules/services/networking/dhcpd.nix
+++ b/nixos/modules/services/networking/dhcpd.nix
@@ -77,7 +77,7 @@ let
       hostName = mkOption {
         type = types.str;
         example = "foo";
-        description = ''
+        description = lib.mdDoc ''
           Hostname which is assigned statically to the machine.
         '';
       };
@@ -85,7 +85,7 @@ let
       ethernetAddress = mkOption {
         type = types.str;
         example = "00:16:76:9a:32:1d";
-        description = ''
+        description = lib.mdDoc ''
           MAC address of the machine.
         '';
       };
@@ -93,7 +93,7 @@ let
       ipAddress = mkOption {
         type = types.str;
         example = "192.168.1.10";
-        description = ''
+        description = lib.mdDoc ''
           IP address of the machine.
         '';
       };
@@ -106,7 +106,7 @@ let
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the DHCPv${postfix} server.
       '';
     };
@@ -124,7 +124,7 @@ let
           range 192.168.1.100 192.168.1.200;
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra text to be appended to the DHCP server configuration
         file. Currently, you almost certainly need to specify something
         there, such as the options specifying the subnet mask, DNS servers,
@@ -135,7 +135,7 @@ let
     extraFlags = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Additional command line flags to be passed to the dhcpd daemon.
       '';
     };
@@ -143,7 +143,7 @@ let
     configFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The path of the DHCP server configuration file.  If no file
         is specified, a file is generated using the other options.
       '';
@@ -152,7 +152,7 @@ let
     interfaces = mkOption {
       type = types.listOf types.str;
       default = ["eth0"];
-      description = ''
+      description = lib.mdDoc ''
         The interfaces on which the DHCP server should listen.
       '';
     };
@@ -170,7 +170,7 @@ let
           ipAddress = "192.168.1.11";
         }
       ];
-      description = ''
+      description = lib.mdDoc ''
         A list mapping Ethernet addresses to IPv${postfix} addresses for the
         DHCP server.
       '';
@@ -179,7 +179,7 @@ let
     authoritative = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether the DHCP server shall send DHCPNAK messages to misconfigured
         clients. If this is not done, clients may be unable to get a correct
         IP address after changing subnets until their old lease has expired.
diff --git a/nixos/modules/services/networking/dnscache.nix b/nixos/modules/services/networking/dnscache.nix
index 7452210de47f..eff13f69f470 100644
--- a/nixos/modules/services/networking/dnscache.nix
+++ b/nixos/modules/services/networking/dnscache.nix
@@ -38,26 +38,26 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to run the dnscache caching dns server.";
+        description = lib.mdDoc "Whether to run the dnscache caching dns server.";
       };
 
       ip = mkOption {
         default = "0.0.0.0";
         type = types.str;
-        description = "IP address on which to listen for connections.";
+        description = lib.mdDoc "IP address on which to listen for connections.";
       };
 
       clientIps = mkOption {
         default = [ "127.0.0.1" ];
         type = types.listOf types.str;
-        description = "Client IP addresses (or prefixes) from which to accept connections.";
+        description = lib.mdDoc "Client IP addresses (or prefixes) from which to accept connections.";
         example = ["192.168" "172.23.75.82"];
       };
 
       domainServers = mkOption {
         default = { };
         type = types.attrsOf (types.listOf types.str);
-        description = ''
+        description = lib.mdDoc ''
           Table of {hostname: server} pairs to use as authoritative servers for hosts (and subhosts).
           If entry for @ is not specified predefined list of root servers is used.
         '';
@@ -72,7 +72,7 @@ in {
       forwardOnly = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to treat root servers (for @) as caching
           servers, requesting addresses the same way a client does. This is
           needed if you want to use e.g. Google DNS as your upstream DNS.
diff --git a/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixos/modules/services/networking/dnscrypt-proxy2.nix
index 316e6e37f9da..de1ca0d2f206 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy2.nix
+++ b/nixos/modules/services/networking/dnscrypt-proxy2.nix
@@ -6,12 +6,12 @@ in
 
 {
   options.services.dnscrypt-proxy2 = {
-    enable = mkEnableOption "dnscrypt-proxy2";
+    enable = mkEnableOption (lib.mdDoc "dnscrypt-proxy2");
 
     settings = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Attrset that is converted and passed as TOML config file.
-        For available params, see: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/${pkgs.dnscrypt-proxy2.version}/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>
+        For available params, see: <https://github.com/DNSCrypt/dnscrypt-proxy/blob/${pkgs.dnscrypt-proxy2.version}/dnscrypt-proxy/example-dnscrypt-proxy.toml>
       '';
       example = literalExpression ''
         {
@@ -28,8 +28,8 @@ in
     };
 
     upstreamDefaults = mkOption {
-      description = ''
-        Whether to base the config declared in <option>services.dnscrypt-proxy2.settings</option> on the upstream example config (<link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>)
+      description = lib.mdDoc ''
+        Whether to base the config declared in {option}`services.dnscrypt-proxy2.settings` on the upstream example config (<https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml>)
 
         Disable this if you want to declare your dnscrypt config from scratch.
       '';
@@ -38,8 +38,8 @@ in
     };
 
     configFile = mkOption {
-      description = ''
-        Path to TOML config file. See: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>
+      description = lib.mdDoc ''
+        Path to TOML config file. See: <https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml>
         If this option is set, it will override any configuration done in options.services.dnscrypt-proxy2.settings.
       '';
       example = "/etc/dnscrypt-proxy/dnscrypt-proxy.toml";
@@ -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/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixos/modules/services/networking/dnscrypt-wrapper.nix
index c2add170e9cc..082e0195093e 100644
--- a/nixos/modules/services/networking/dnscrypt-wrapper.nix
+++ b/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -124,20 +124,20 @@ in {
   ###### interface
 
   options.services.dnscrypt-wrapper = {
-    enable = mkEnableOption "DNSCrypt wrapper";
+    enable = mkEnableOption (lib.mdDoc "DNSCrypt wrapper");
 
     address = mkOption {
       type = types.str;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         The DNSCrypt wrapper will bind to this IP address.
       '';
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 5353;
-      description = ''
+      description = lib.mdDoc ''
         The DNSCrypt wrapper will listen for DNS queries on this port.
       '';
     };
@@ -147,9 +147,9 @@ in {
       default = "2.dnscrypt-cert.${config.networking.hostName}";
       defaultText = literalExpression ''"2.dnscrypt-cert.''${config.networking.hostName}"'';
       example = "2.dnscrypt-cert.myresolver";
-      description = ''
+      description = lib.mdDoc ''
         The name that will be given to this DNSCrypt resolver.
-        Note: the resolver name must start with <literal>2.dnscrypt-cert.</literal>.
+        Note: the resolver name must start with `2.dnscrypt-cert.`.
       '';
     };
 
@@ -157,7 +157,7 @@ in {
       type = types.nullOr types.path;
       default = null;
       example = "/etc/secrets/public.key";
-      description = ''
+      description = lib.mdDoc ''
         The filepath to the provider public key. If not given a new
         provider key pair will be generated on the first run.
       '';
@@ -167,7 +167,7 @@ in {
       type = types.nullOr types.path;
       default = null;
       example = "/etc/secrets/secret.key";
-      description = ''
+      description = lib.mdDoc ''
         The filepath to the provider secret key. If not given a new
         provider key pair will be generated on the first run.
       '';
@@ -176,15 +176,15 @@ in {
     upstream.address = mkOption {
       type = types.str;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         The IP address of the upstream DNS server DNSCrypt will "wrap".
       '';
     };
 
     upstream.port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 53;
-      description = ''
+      description = lib.mdDoc ''
         The port of the upstream DNS server DNSCrypt will "wrap".
       '';
     };
@@ -192,7 +192,7 @@ in {
     keys.expiration = mkOption {
       type = types.int;
       default = 30;
-      description = ''
+      description = lib.mdDoc ''
         The duration (in days) of the time-limited secret key.
         This will be automatically rotated before expiration.
       '';
@@ -201,7 +201,7 @@ in {
     keys.checkInterval = mkOption {
       type = types.int;
       default = 1440;
-      description = ''
+      description = lib.mdDoc ''
         The time interval (in minutes) between key expiration checks.
       '';
     };
diff --git a/nixos/modules/services/networking/dnsdist.nix b/nixos/modules/services/networking/dnsdist.nix
index c7c6a79864cb..483300111df9 100644
--- a/nixos/modules/services/networking/dnsdist.nix
+++ b/nixos/modules/services/networking/dnsdist.nix
@@ -11,23 +11,23 @@ let
 in {
   options = {
     services.dnsdist = {
-      enable = mkEnableOption "dnsdist domain name server";
+      enable = mkEnableOption (lib.mdDoc "dnsdist domain name server");
 
       listenAddress = mkOption {
         type = types.str;
-        description = "Listen IP Address";
+        description = lib.mdDoc "Listen IP Address";
         default = "0.0.0.0";
       };
       listenPort = mkOption {
         type = types.int;
-        description = "Listen port";
+        description = lib.mdDoc "Listen port";
         default = 53;
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to dnsdist.conf.
         '';
       };
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 59a3ca2f28e3..4886654e8c03 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/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 = {
@@ -32,7 +48,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run dnsmasq.
         '';
       };
@@ -40,35 +56,63 @@ in
       resolveLocalQueries = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether dnsmasq should resolve local queries (i.e. add 127.0.0.1 to
           /etc/resolv.conf).
         '';
       };
 
-      servers = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "8.8.8.8" "8.8.4.4" ];
-        description = ''
-          The DNS servers which dnsmasq should query.
-        '';
-      };
-
       alwaysKeepRunning = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, systemd will always respawn dnsmasq even if shut down manually. The default, disabled, will only restart it on error.
         '';
       };
 
+      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 = ''
+        description = lib.mdDoc ''
           Extra configuration directives that should be added to
-          <literal>dnsmasq.conf</literal>.
+          `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/nixos/modules/services/networking/doh-proxy-rust.nix b/nixos/modules/services/networking/doh-proxy-rust.nix
index efd492e23f8c..7f8bbb8a7699 100644
--- a/nixos/modules/services/networking/doh-proxy-rust.nix
+++ b/nixos/modules/services/networking/doh-proxy-rust.nix
@@ -10,15 +10,15 @@ 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;
       default = [];
       example = [ "--server-address=9.9.9.9:53" ];
-      description = ''
+      description = lib.mdDoc ''
         A list of command-line flags to pass to doh-proxy. For details on the
-        available options, see <link xlink:href="https://github.com/jedisct1/doh-server#usage"/>.
+        available options, see <https://github.com/jedisct1/doh-server#usage>.
       '';
     };
 
diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix
index daf8d5c42475..3feafc3bb3bd 100644
--- a/nixos/modules/services/networking/ejabberd.nix
+++ b/nixos/modules/services/networking/ejabberd.nix
@@ -26,63 +26,63 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable ejabberd server";
+        description = lib.mdDoc "Whether to enable ejabberd server";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.ejabberd;
         defaultText = literalExpression "pkgs.ejabberd";
-        description = "ejabberd server package to use";
+        description = lib.mdDoc "ejabberd server package to use";
       };
 
       user = mkOption {
         type = types.str;
         default = "ejabberd";
-        description = "User under which ejabberd is ran";
+        description = lib.mdDoc "User under which ejabberd is ran";
       };
 
       group = mkOption {
         type = types.str;
         default = "ejabberd";
-        description = "Group under which ejabberd is ran";
+        description = lib.mdDoc "Group under which ejabberd is ran";
       };
 
       spoolDir = mkOption {
         type = types.path;
         default = "/var/lib/ejabberd";
-        description = "Location of the spooldir of ejabberd";
+        description = lib.mdDoc "Location of the spooldir of ejabberd";
       };
 
       logsDir = mkOption {
         type = types.path;
         default = "/var/log/ejabberd";
-        description = "Location of the logfile directory of ejabberd";
+        description = lib.mdDoc "Location of the logfile directory of ejabberd";
       };
 
       configFile = mkOption {
         type = types.nullOr types.path;
-        description = "Configuration file for ejabberd in YAML format";
+        description = lib.mdDoc "Configuration file for ejabberd in YAML format";
         default = null;
       };
 
       ctlConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Configuration of ejabberdctl";
+        description = lib.mdDoc "Configuration of ejabberdctl";
       };
 
       loadDumps = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = "Configuration dumps that should be loaded on the first startup";
+        description = lib.mdDoc "Configuration dumps that should be loaded on the first startup";
         example = literalExpression "[ ./myejabberd.dump ]";
       };
 
       imagemagick = mkOption {
         type = types.bool;
         default = false;
-        description = "Add ImageMagick to server's path; allows for image thumbnailing";
+        description = lib.mdDoc "Add ImageMagick to server's path; allows for image thumbnailing";
       };
     };
 
diff --git a/nixos/modules/services/networking/envoy.nix b/nixos/modules/services/networking/envoy.nix
index b7f859c73d9d..20cfebb79914 100644
--- a/nixos/modules/services/networking/envoy.nix
+++ b/nixos/modules/services/networking/envoy.nix
@@ -16,7 +16,7 @@ in
 
 {
   options.services.envoy = {
-    enable = mkEnableOption "Envoy reverse proxy";
+    enable = mkEnableOption (lib.mdDoc "Envoy reverse proxy");
 
     settings = mkOption {
       type = format.type;
@@ -39,7 +39,7 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Specify the configuration for Envoy in Nix.
       '';
     };
diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix
index 75d78476e578..0bc8c71f4eaa 100644
--- a/nixos/modules/services/networking/epmd.nix
+++ b/nixos/modules/services/networking/epmd.nix
@@ -11,7 +11,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable socket activation for Erlang Port Mapper Daemon (epmd),
         which acts as a name server on all hosts involved in distributed
         Erlang computations.
@@ -21,7 +21,7 @@ in
       type = types.package;
       default = pkgs.erlang;
       defaultText = literalExpression "pkgs.erlang";
-      description = ''
+      description = lib.mdDoc ''
         The Erlang package to use to get epmd binary. That way you can re-use
         an Erlang runtime that is already installed for other purposes.
       '';
@@ -30,9 +30,9 @@ in
       {
         type = types.str;
         default = "[::]:4369";
-        description = ''
+        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/nixos/modules/services/networking/ergo.nix b/nixos/modules/services/networking/ergo.nix
index 6e55a7cfff6c..033d4d9caf8a 100644
--- a/nixos/modules/services/networking/ergo.nix
+++ b/nixos/modules/services/networking/ergo.nix
@@ -33,25 +33,25 @@ in {
   options = {
 
     services.ergo = {
-      enable = mkEnableOption "Ergo service";
+      enable = mkEnableOption (lib.mdDoc "Ergo service");
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/ergo";
-        description = "The data directory for the Ergo node.";
+        description = lib.mdDoc "The data directory for the Ergo node.";
       };
 
       listen = {
         ip = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = "IP address on which the Ergo node should listen.";
+          description = lib.mdDoc "IP address on which the Ergo node should listen.";
         };
 
         port = mkOption {
           type = types.port;
           default = 9006;
-          description = "Listen port for the Ergo node.";
+          description = lib.mdDoc "Listen port for the Ergo node.";
         };
       };
 
@@ -60,20 +60,20 @@ in {
         type = types.nullOr types.str;
         default = null;
         example = "324dcf027dd4a30a932c441f365a25e86b173defa4b8e58948253471b81b72cf";
-        description = "Hex-encoded Blake2b256 hash of an API key as a 64-chars long Base16 string.";
+        description = lib.mdDoc "Hex-encoded Blake2b256 hash of an API key as a 64-chars long Base16 string.";
        };
 
        listen = {
         ip = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = "IP address that the Ergo node API should listen on if <option>api.keyHash</option> is defined.";
+          description = lib.mdDoc "IP address that the Ergo node API should listen on if {option}`api.keyHash` is defined.";
           };
 
         port = mkOption {
           type = types.port;
           default = 9052;
-          description = "Listen port for the API endpoint if <option>api.keyHash</option> is defined.";
+          description = lib.mdDoc "Listen port for the API endpoint if {option}`api.keyHash` is defined.";
         };
        };
       };
@@ -81,26 +81,26 @@ in {
       testnet = mkOption {
          type = types.bool;
          default = false;
-         description = "Connect to testnet network instead of the default mainnet.";
+         description = lib.mdDoc "Connect to testnet network instead of the default mainnet.";
       };
 
       user = mkOption {
         type = types.str;
         default = "ergo";
-        description = "The user as which to run the Ergo node.";
+        description = lib.mdDoc "The user as which to run the Ergo node.";
       };
 
       group = mkOption {
         type = types.str;
         default = cfg.user;
         defaultText = literalExpression "config.${opt.user}";
-        description = "The group as which to run the Ergo node.";
+        description = lib.mdDoc "The group as which to run the Ergo node.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the Ergo node as well as the API.";
+        description = lib.mdDoc "Open ports in the firewall for the Ergo node as well as the API.";
       };
     };
   };
diff --git a/nixos/modules/services/networking/ergochat.nix b/nixos/modules/services/networking/ergochat.nix
index cfaf69fc6139..a003512677eb 100644
--- a/nixos/modules/services/networking/ergochat.nix
+++ b/nixos/modules/services/networking/ergochat.nix
@@ -4,12 +4,12 @@ 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;
         default = 1024;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of open files. Limits the clients and server connections.
         '';
       };
@@ -17,16 +17,16 @@ 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>";
-        description = ''
+        defaultText = lib.literalMD "generated config file from `settings`";
+        description = lib.mdDoc ''
           Path to configuration file.
-          Setting this will skip any configuration done via <literal>.settings</literal>
+          Setting this will skip any configuration done via `settings`
         '';
       };
 
       settings = lib.mkOption {
         type = (pkgs.formats.yaml {}).type;
-        description = ''
+        description = lib.mdDoc ''
           Ergo IRC daemon configuration file.
           https://raw.githubusercontent.com/ergochat/ergo/master/default.yaml
         '';
diff --git a/nixos/modules/services/networking/eternal-terminal.nix b/nixos/modules/services/networking/eternal-terminal.nix
index 0dcf3d28f4e0..c6b6b04dcf72 100644
--- a/nixos/modules/services/networking/eternal-terminal.nix
+++ b/nixos/modules/services/networking/eternal-terminal.nix
@@ -16,12 +16,12 @@ in
 
     services.eternal-terminal = {
 
-      enable = mkEnableOption "Eternal Terminal server";
+      enable = mkEnableOption (lib.mdDoc "Eternal Terminal server");
 
       port = mkOption {
         default = 2022;
-        type = types.int;
-        description = ''
+        type = types.port;
+        description = lib.mdDoc ''
           The port the server should listen on. Will use the server's default (2022) if not specified.
 
           Make sure to open this port in the firewall if necessary.
@@ -31,7 +31,7 @@ in
       verbosity = mkOption {
         default = 0;
         type = types.enum (lib.range 0 9);
-        description = ''
+        description = lib.mdDoc ''
           The verbosity level (0-9).
         '';
       };
@@ -39,7 +39,7 @@ in
       silent = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, disables all logging.
         '';
       };
@@ -47,7 +47,7 @@ in
       logSize = mkOption {
         default = 20971520;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           The maximum log size.
         '';
       };
diff --git a/nixos/modules/services/networking/expressvpn.nix b/nixos/modules/services/networking/expressvpn.nix
new file mode 100644
index 000000000000..30de6987d31f
--- /dev/null
+++ b/nixos/modules/services/networking/expressvpn.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+{
+  options.services.expressvpn.enable = mkOption {
+    type = types.bool;
+    default = false;
+    description = lib.mdDoc ''
+      Enable the ExpressVPN daemon.
+    '';
+  };
+
+  config = mkIf config.services.expressvpn.enable {
+    boot.kernelModules = [ "tun" ];
+
+    systemd.services.expressvpn = {
+      description = "ExpressVPN Daemon";
+      serviceConfig = {
+        ExecStart = "${pkgs.expressvpn}/bin/expressvpnd";
+        Restart = "on-failure";
+        RestartSec = 5;
+      };
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "network-online.target" ];
+    };
+  };
+
+  meta.maintainers = with maintainers; [ yureien ];
+}
diff --git a/nixos/modules/services/networking/fakeroute.nix b/nixos/modules/services/networking/fakeroute.nix
index 7916ad4098a7..ed6b1a3c4d26 100644
--- a/nixos/modules/services/networking/fakeroute.nix
+++ b/nixos/modules/services/networking/fakeroute.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the fakeroute service.
         '';
       };
@@ -33,7 +33,7 @@ in
           "198.116.142.34"
           "63.199.8.242"
         ];
-        description = ''
+        description = lib.mdDoc ''
          Fake route that will appear after the real
          one to any host running a traceroute.
         '';
diff --git a/nixos/modules/services/networking/ferm.nix b/nixos/modules/services/networking/ferm.nix
index 8e03f30efc00..09151eb0b544 100644
--- a/nixos/modules/services/networking/ferm.nix
+++ b/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
@@ -28,13 +28,13 @@ in {
         '';
       };
       config = mkOption {
-        description = "Verbatim ferm.conf configuration.";
+        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 {
-        description = "The ferm package.";
+        description = lib.mdDoc "The ferm package.";
         type = types.package;
         default = pkgs.ferm;
         defaultText = literalExpression "pkgs.ferm";
diff --git a/nixos/modules/services/networking/firefox-syncserver.md b/nixos/modules/services/networking/firefox-syncserver.md
new file mode 100644
index 000000000000..3ee863343ece
--- /dev/null
+++ b/nixos/modules/services/networking/firefox-syncserver.md
@@ -0,0 +1,55 @@
+# Firefox Sync server {#module-services-firefox-syncserver}
+
+A storage server for Firefox Sync that you can easily host yourself.
+
+## Quickstart {#module-services-firefox-syncserver-quickstart}
+
+The absolute minimal configuration for the sync server looks like this:
+
+```nix
+services.mysql.package = pkgs.mariadb;
+
+services.firefox-syncserver = {
+  enable = true;
+  secrets = builtins.toFile "sync-secrets" ''
+    SYNC_MASTER_SECRET=this-secret-is-actually-leaked-to-/nix/store
+  '';
+  singleNode = {
+    enable = true;
+    hostname = "localhost";
+    url = "http://localhost:5000";
+  };
+};
+```
+
+This will start a sync server that is only accessible locally. Once the services is
+running you can navigate to `about:config` in your Firefox profile and set
+`identity.sync.tokenserver.uri` to `http://localhost:5000/1.0/sync/1.5`. Your browser
+will now use your local sync server for data storage.
+
+::: {.warning}
+This configuration should never be used in production. It is not encrypted and
+stores its secrets in a world-readable location.
+:::
+
+## More detailed setup {#module-services-firefox-syncserver-configuration}
+
+The `firefox-syncserver` service provides a number of options to make setting up
+small deployment easier. These are grouped under the `singleNode` element of the
+option tree and allow simple configuration of the most important parameters.
+
+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 `capacity`, which configures how many accounts may be active on
+this instance, and `url`, which holds the URL under which the sync server can be
+accessed. The `url` can be configured automatically when using nginx.
+
+Options that affect the surroundings of the sync server are `enableNginx`,
+`enableTLS` and `hostnam`. If `enableNginx` is set the sync server module will
+automatically add an nginx virtual host to the system using `hostname` as the
+domain and set `url` accordingly. If `enableTLS` is set the module will also
+enable ACME certificates on the new virtual host and force all connections to
+be made via TLS.
+
+For actual deployment it is also recommended to store the `secrets` file in a
+secure location.
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
new file mode 100644
index 000000000000..9733fb16d903
--- /dev/null
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -0,0 +1,318 @@
+{ config, pkgs, lib, options, ... }:
+
+let
+  cfg = config.services.firefox-syncserver;
+  opt = options.services.firefox-syncserver;
+  defaultDatabase = "firefox_syncserver";
+  defaultUser = "firefox-syncserver";
+
+  dbIsLocal = cfg.database.host == "localhost";
+  dbURL = "mysql://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}";
+
+  format = pkgs.formats.toml {};
+  settings = {
+    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
+      # accounts are removed if the total capacity is 10 or larger to begin
+      # with.
+      # https://github.com/mozilla-services/syncstorage-rs/issues/1313#issuecomment-1145293375
+      node_capacity_release_rate = 1;
+    };
+  };
+  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 (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
+
+        ```
+          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');
+        ```
+
+        {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 = lib.mdDoc ''
+          Package to use.
+        '';
+      };
+
+      database.name = lib.mkOption {
+        # the mysql module does not allow `-quoting without resorting to shell
+        # escaping, so we restrict db names for forward compaitiblity should this
+        # behavior ever change.
+        type = lib.types.strMatching "[a-z_][a-z0-9_]*";
+        default = defaultDatabase;
+        description = lib.mdDoc ''
+          Database to use for storage. Will be created automatically if it does not exist
+          and `config.${opt.database.createLocally}` is set.
+        '';
+      };
+
+      database.user = lib.mkOption {
+        type = lib.types.str;
+        default = defaultUser;
+        description = lib.mdDoc ''
+          Username for database connections.
+        '';
+      };
+
+      database.host = lib.mkOption {
+        type = lib.types.str;
+        default = "localhost";
+        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.
+        '';
+      };
+
+      database.createLocally = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        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.
+        '';
+      };
+
+      logLevel = lib.mkOption {
+        type = lib.types.str;
+        default = "error";
+        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 = lib.mdDoc ''
+          A file containing the various secrets. Should be in the format expected by systemd's
+          `EnvironmentFile` directory. Two secrets are currently available:
+          `SYNC_MASTER_SECRET` and
+          `SYNC_TOKENSERVER__FXA_METRICS_HASH_SECRET`.
+        '';
+      };
+
+      singleNode = {
+        enable = lib.mkEnableOption (lib.mdDoc "auto-configuration for a simple single-node setup");
+
+        enableTLS = lib.mkEnableOption (lib.mdDoc "automatic TLS setup");
+
+        enableNginx = lib.mkEnableOption (lib.mdDoc "nginx virtualhost definitions");
+
+        hostname = lib.mkOption {
+          type = lib.types.str;
+          description = lib.mdDoc ''
+            Host name to use for this service.
+          '';
+        };
+
+        capacity = lib.mkOption {
+          type = lib.types.ints.unsigned;
+          default = 10;
+          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.
+          '';
+        };
+
+        url = lib.mkOption {
+          type = lib.types.str;
+          default = "${if cfg.singleNode.enableTLS then "https" else "http"}://${cfg.singleNode.hostname}";
+          defaultText = lib.literalExpression ''
+            ''${if cfg.singleNode.enableTLS then "https" else "http"}://''${config.${opt.singleNode.hostname}}
+          '';
+          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.
+          '';
+        };
+      };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule {
+          freeformType = format.type;
+
+          options = {
+            port = lib.mkOption {
+              type = lib.types.port;
+              default = 5000;
+              description = lib.mdDoc ''
+                Port to bind to.
+              '';
+            };
+
+            tokenserver.enabled = lib.mkOption {
+              type = lib.types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Whether to enable the token service as well.
+              '';
+            };
+          };
+        };
+        default = { };
+        description = lib.mdDoc ''
+          Settings for the sync server. These take priority over values computed
+          from NixOS options.
+
+          See the doc comments on the `Settings` structs in
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/settings.rs>
+          and
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/tokenserver/settings.rs>
+          for available options.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.mysql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = {
+          "${cfg.database.name}.*" = "all privileges";
+        };
+      }];
+    };
+
+    systemd.services.firefox-syncserver = {
+      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/syncserver --config ${configFile}";
+        EnvironmentFile = lib.mkIf (cfg.secrets != null) "${cfg.secrets}";
+
+        # hardening
+        RemoveIPC = true;
+        CapabilityBoundingSet = [ "" ];
+        DynamicUser = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        ProtectClock = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        SystemCallArchitectures = "native";
+        # syncstorage-rs uses python-cffi internally, and python-cffi does not
+        # work with MemoryDenyWriteExecute=true
+        MemoryDenyWriteExecute = false;
+        RestrictNamespaces = true;
+        RestrictSUIDSGID = true;
+        ProtectHostname = true;
+        LockPersonality = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictRealtime = true;
+        ProtectSystem = "strict";
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectHome = true;
+        PrivateUsers = true;
+        PrivateTmp = true;
+        SystemCallFilter = [ "@system-service" "~ @privileged @resources" ];
+        UMask = "0077";
+      };
+    };
+
+    systemd.services.firefox-syncserver-setup = lib.mkIf cfg.singleNode.enable {
+      wantedBy = [ "firefox-syncserver.service" ];
+      requires = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
+      after = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
+      path = [ config.services.mysql.package ];
+      serviceConfig.ExecStart = [ "${setupScript}" ];
+    };
+
+    services.nginx.virtualHosts = lib.mkIf cfg.singleNode.enableNginx {
+      ${cfg.singleNode.hostname} = {
+        enableACME = cfg.singleNode.enableTLS;
+        forceSSL = cfg.singleNode.enableTLS;
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.settings.port}";
+        };
+      };
+    };
+  };
+
+  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;
+  };
+}
diff --git a/nixos/modules/services/networking/firefox-syncserver.xml b/nixos/modules/services/networking/firefox-syncserver.xml
new file mode 100644
index 000000000000..66c812266951
--- /dev/null
+++ b/nixos/modules/services/networking/firefox-syncserver.xml
@@ -0,0 +1,77 @@
+<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/nixos/modules/services/networking/fireqos.nix b/nixos/modules/services/networking/fireqos.nix
index 0b34f0b6b8b4..b7f51a89c0e1 100644
--- a/nixos/modules/services/networking/fireqos.nix
+++ b/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`.
       '';
@@ -28,7 +28,7 @@ in {
           class web commit 50kbit
             match tcp ports 80,443
       '';
-      description = ''
+      description = lib.mdDoc ''
         The FireQOS configuration goes here.
       '';
     };
diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix
index c213a5516a49..27119dcc57c5 100644
--- a/nixos/modules/services/networking/firewall.nix
+++ b/nixos/modules/services/networking/firewall.nix
@@ -16,7 +16,7 @@
      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,
+   - ‘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.
@@ -109,28 +109,28 @@ let
     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
+    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 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
+      # 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 raw -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN
+      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 raw -A nixos-fw-rpfilter -s 0.0.0.0 -d 255.255.255.255 -p udp --sport 68 --dport 67 -j RETURN
+      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 raw -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
+        ip46tables -t mangle -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
       ''}
-      ip46tables -t raw -A nixos-fw-rpfilter -j DROP
+      ip46tables -t mangle -A nixos-fw-rpfilter -j DROP
 
-      ip46tables -t raw -A PREROUTING -j nixos-fw-rpfilter
+      ip46tables -t mangle -A PREROUTING -j nixos-fw-rpfilter
     ''}
 
     # Accept all traffic on the trusted interfaces.
@@ -218,7 +218,7 @@ let
     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
+      ip46tables -t mangle -D PREROUTING -j nixos-fw-rpfilter 2>/dev/null || true
     ''}
 
     ${cfg.extraStopCommands}
@@ -258,7 +258,7 @@ let
       apply = canonicalizePortList;
       example = [ 22 80 ];
       description =
-        ''
+        lib.mdDoc ''
           List of TCP ports on which incoming connections are
           accepted.
         '';
@@ -269,7 +269,7 @@ let
       default = [ ];
       example = [ { from = 8999; to = 9003; } ];
       description =
-        ''
+        lib.mdDoc ''
           A range of TCP ports on which incoming connections are
           accepted.
         '';
@@ -281,7 +281,7 @@ let
       apply = canonicalizePortList;
       example = [ 53 ];
       description =
-        ''
+        lib.mdDoc ''
           List of open UDP ports.
         '';
     };
@@ -291,7 +291,7 @@ let
       default = [ ];
       example = [ { from = 60000; to = 61000; } ];
       description =
-        ''
+        lib.mdDoc ''
           Range of open UDP ports.
         '';
     };
@@ -310,7 +310,7 @@ in
         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
@@ -324,8 +324,8 @@ in
         defaultText = literalExpression "pkgs.iptables";
         example = literalExpression "pkgs.iptables-legacy";
         description =
-          ''
-            The iptables package to use for running the firewall service."
+          lib.mdDoc ''
+            The iptables package to use for running the firewall service.
           '';
       };
 
@@ -333,7 +333,7 @@ in
         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.
@@ -344,7 +344,7 @@ in
         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.
@@ -357,8 +357,8 @@ in
         type = types.bool;
         default = true;
         description =
-          ''
-            If <option>networking.firewall.logRefusedPackets</option>
+          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.
@@ -369,7 +369,7 @@ in
         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
@@ -383,7 +383,7 @@ in
         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.
@@ -394,7 +394,7 @@ in
         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
@@ -407,7 +407,7 @@ in
         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"
@@ -417,10 +417,10 @@ in
       checkReversePath = mkOption {
         type = types.either types.bool (types.enum ["strict" "loose"]);
         default = kernelHasRPFilter;
-        defaultText = literalDocBook "<literal>true</literal> if supported by the chosen kernel";
+        defaultText = literalMD "`true` if supported by the chosen kernel";
         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.
@@ -440,7 +440,7 @@ in
         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.
           '';
@@ -451,7 +451,7 @@ in
         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.
 
@@ -471,7 +471,7 @@ in
         type = types.bool;
         default = false;
         description =
-          ''
+          lib.mdDoc ''
             Whether to auto-load connection-tracking helpers.
             See the description at networking.firewall.connectionTrackingModules
 
@@ -484,7 +484,7 @@ in
         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
@@ -497,7 +497,7 @@ in
         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.
           '';
@@ -508,7 +508,7 @@ in
         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
@@ -520,7 +520,7 @@ in
         default = { };
         type = with types; attrsOf (submodule [ { options = commonOptions; } ]);
         description =
-          ''
+          lib.mdDoc ''
             Interface-specific open ports.
           '';
       };
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index ac84b3d35a3d..6ed4f78ddc92 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -14,17 +14,17 @@ let
   };
 in {
   options.services.flannel = {
-    enable = mkEnableOption "flannel";
+    enable = mkEnableOption (lib.mdDoc "flannel");
 
     package = mkOption {
-      description = "Package to use for flannel";
+      description = lib.mdDoc "Package to use for flannel";
       type = types.package;
       default = pkgs.flannel;
       defaultText = literalExpression "pkgs.flannel";
     };
 
     publicIp = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         IP accessible by other nodes for inter-host communication.
         Defaults to the IP of the interface being used for communication.
       '';
@@ -33,7 +33,7 @@ in {
     };
 
     iface = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Interface to use (IP or name) for inter-host communication.
         Defaults to the interface for the default route on the machine.
       '';
@@ -43,38 +43,38 @@ in {
 
     etcd = {
       endpoints = mkOption {
-        description = "Etcd endpoints";
+        description = lib.mdDoc "Etcd endpoints";
         type = types.listOf types.str;
         default = ["http://127.0.0.1:2379"];
       };
 
       prefix = mkOption {
-        description = "Etcd key prefix";
+        description = lib.mdDoc "Etcd key prefix";
         type = types.str;
         default = "/coreos.com/network";
       };
 
       caFile = mkOption {
-        description = "Etcd certificate authority file";
+        description = lib.mdDoc "Etcd certificate authority file";
         type = types.nullOr types.path;
         default = null;
       };
 
       certFile = mkOption {
-        description = "Etcd cert file";
+        description = lib.mdDoc "Etcd cert file";
         type = types.nullOr types.path;
         default = null;
       };
 
       keyFile = mkOption {
-        description = "Etcd key file";
+        description = lib.mdDoc "Etcd key file";
         type = types.nullOr types.path;
         default = null;
       };
     };
 
     kubeconfig = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Path to kubeconfig to use for storing flannel config using the
         Kubernetes API
       '';
@@ -83,30 +83,28 @@ 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;
     };
 
     nodeName = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         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";
     };
 
     storageBackend = mkOption {
-      description = "Determines where flannel stores its configuration at runtime";
+      description = lib.mdDoc "Determines where flannel stores its configuration at runtime";
       type = types.enum ["etcd" "kubernetes"];
       default = "etcd";
     };
 
     subnetLen = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The size of the subnet allocated to each host. Defaults to 24 (i.e. /24)
         unless the Network was configured to be smaller than a /24 in which case
         it is one less than the network.
@@ -116,7 +114,7 @@ in {
     };
 
     subnetMin = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The beginning of IP range which the subnet allocation should start with.
         Defaults to the first subnet of Network.
       '';
@@ -125,7 +123,7 @@ in {
     };
 
     subnetMax = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The end of IP range which the subnet allocation should start with.
         Defaults to the last subnet of Network.
       '';
@@ -134,7 +132,7 @@ in {
     };
 
     backend = mkOption {
-      description = "Type of backend to use and specific configurations for that backend.";
+      description = lib.mdDoc "Type of backend to use and specific configurations for that backend.";
       type = types.attrs;
       default = {
         Type = "vxlan";
@@ -155,10 +153,11 @@ in {
         FLANNELD_ETCD_KEYFILE = cfg.etcd.keyFile;
         FLANNELD_ETCD_CERTFILE = cfg.etcd.certFile;
         FLANNELD_ETCD_CAFILE = cfg.etcd.caFile;
-        ETCDCTL_CERT_FILE = cfg.etcd.certFile;
-        ETCDCTL_KEY_FILE = cfg.etcd.keyFile;
-        ETCDCTL_CA_FILE = cfg.etcd.caFile;
-        ETCDCTL_PEERS = concatStringsSep "," cfg.etcd.endpoints;
+        ETCDCTL_CERT = cfg.etcd.certFile;
+        ETCDCTL_KEY = cfg.etcd.keyFile;
+        ETCDCTL_CACERT = cfg.etcd.caFile;
+        ETCDCTL_ENDPOINTS = concatStringsSep "," cfg.etcd.endpoints;
+        ETCDCTL_API = "3";
       } // optionalAttrs (cfg.storageBackend == "kubernetes") {
         FLANNELD_KUBE_SUBNET_MGR = "true";
         FLANNELD_KUBECONFIG_FILE = cfg.kubeconfig;
@@ -167,7 +166,7 @@ in {
       path = [ pkgs.iptables ];
       preStart = optionalString (cfg.storageBackend == "etcd") ''
         echo "setting network configuration"
-        until ${pkgs.etcd}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
+        until ${pkgs.etcd}/bin/etcdctl put /coreos.com/network/config '${builtins.toJSON networkConfig}'
         do
           echo "setting network configuration, retry"
           sleep 1
diff --git a/nixos/modules/services/networking/freenet.nix b/nixos/modules/services/networking/freenet.nix
index 3da3ab0c7df4..e1737e820a51 100644
--- a/nixos/modules/services/networking/freenet.nix
+++ b/nixos/modules/services/networking/freenet.nix
@@ -22,13 +22,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable the Freenet daemon";
+        description = lib.mdDoc "Enable the Freenet daemon";
       };
 
       nice = mkOption {
         type = types.int;
         default = 10;
-        description = "Set the nice level for the Freenet daemon";
+        description = lib.mdDoc "Set the nice level for the Freenet daemon";
       };
 
     };
diff --git a/nixos/modules/services/networking/freeradius.nix b/nixos/modules/services/networking/freeradius.nix
index 7fa3a8fa17fa..419a683cb774 100644
--- a/nixos/modules/services/networking/freeradius.nix
+++ b/nixos/modules/services/networking/freeradius.nix
@@ -33,12 +33,12 @@ let
   };
 
   freeradiusConfig = {
-    enable = mkEnableOption "the freeradius server";
+    enable = mkEnableOption (lib.mdDoc "the freeradius server");
 
     configDir = mkOption {
       type = types.path;
       default = "/etc/raddb";
-      description = ''
+      description = lib.mdDoc ''
         The path of the freeradius server configuration directory.
       '';
     };
@@ -46,7 +46,7 @@ let
     debug = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable debug logging for freeradius (-xx
         option). This should not be left on, since it includes
         sensitive data such as passwords in the logs.
diff --git a/nixos/modules/services/networking/frr.nix b/nixos/modules/services/networking/frr.nix
index 45a82b9450a4..d350fe3548ae 100644
--- a/nixos/modules/services/networking/frr.nix
+++ b/nixos/modules/services/networking/frr.nix
@@ -51,13 +51,13 @@ 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;
         default = null;
         example = "/etc/frr/${daemonName service}.conf";
-        description = ''
+        description = lib.mdDoc ''
           Configuration file to use for FRR ${daemonName service}.
           By default the NixOS generated files are used.
         '';
@@ -86,7 +86,7 @@ let
             };
           in
             examples.${service} or "";
-        description = ''
+        description = lib.mdDoc ''
           ${daemonName service} configuration statements.
         '';
       };
@@ -94,7 +94,7 @@ let
       vtyListenAddress = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           Address to bind to for the VTY interface.
         '';
       };
@@ -102,10 +102,18 @@ let
       vtyListenPort = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           TCP Port to bind to for the VTY interface.
         '';
       };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = lib.mdDoc ''
+          Extra options for the daemon.
+        '';
+      };
     };
 
 in
@@ -120,7 +128,7 @@ in
           enable = mkOption {
             type = types.bool;
             default = any isEnabled services;
-            description = ''
+            description = lib.mdDoc ''
               Whether to enable the Zebra routing manager.
 
               The Zebra routing manager is automatically enabled
@@ -196,7 +204,8 @@ in
                 PIDFile = "frr/${daemon}.pid";
                 ExecStart = "${pkgs.frr}/libexec/frr/${daemon} -f /etc/frr/${service}.conf"
                   + optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}"
-                  + optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}";
+                  + optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}"
+                  + " " + (concatStringsSep " " scfg.extraOptions);
                 ExecReload = "${pkgs.python3.interpreter} ${pkgs.frr}/libexec/frr/frr-reload.py --reload --daemon ${daemonName service} --bindir ${pkgs.frr}/bin --rundir /run/frr /etc/frr/${service}.conf";
                 Restart = "on-abnormal";
               };
diff --git a/nixos/modules/services/networking/gateone.nix b/nixos/modules/services/networking/gateone.nix
index 3e3a3c1aa94d..ac3f3c9bbf2c 100644
--- a/nixos/modules/services/networking/gateone.nix
+++ b/nixos/modules/services/networking/gateone.nix
@@ -6,16 +6,16 @@ in
 {
 options = {
     services.gateone = {
-      enable = mkEnableOption "GateOne server";
+      enable = mkEnableOption (lib.mdDoc "GateOne server");
       pidDir = mkOption {
         default = "/run/gateone";
         type = types.path;
-        description = "Path of pid files for GateOne.";
+        description = lib.mdDoc "Path of pid files for GateOne.";
       };
       settingsDir = mkOption {
         default = "/var/lib/gateone";
         type = types.path;
-        description = "Path of configuration files for GateOne.";
+        description = lib.mdDoc "Path of configuration files for GateOne.";
       };
     };
 };
@@ -36,11 +36,11 @@ config = mkIf cfg.enable {
     preStart = ''
       if [ ! -d ${cfg.settingsDir} ] ; then
         mkdir -m 0750 -p ${cfg.settingsDir}
-        chown -R gateone.gateone ${cfg.settingsDir}
+        chown -R gateone:gateone ${cfg.settingsDir}
       fi
       if [ ! -d ${cfg.pidDir} ] ; then
         mkdir -m 0750 -p ${cfg.pidDir}
-        chown -R gateone.gateone ${cfg.pidDir}
+        chown -R gateone:gateone ${cfg.pidDir}
       fi
       '';
     #unitConfig.RequiresMountsFor = "${cfg.settingsDir}";
diff --git a/nixos/modules/services/networking/gdomap.nix b/nixos/modules/services/networking/gdomap.nix
index 3d829cb69135..53ea8b6875d8 100644
--- a/nixos/modules/services/networking/gdomap.nix
+++ b/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/nixos/modules/services/networking/ghostunnel.nix b/nixos/modules/services/networking/ghostunnel.nix
index 7a62d378e2c6..4902367e2a6a 100644
--- a/nixos/modules/services/networking/ghostunnel.nix
+++ b/nixos/modules/services/networking/ghostunnel.nix
@@ -23,60 +23,60 @@ let
       options = {
 
         listen = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Address and port to listen on (can be HOST:PORT, unix:PATH).
           '';
           type = types.str;
         };
 
         target = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Address to forward connections to (can be HOST:PORT or unix:PATH).
           '';
           type = types.str;
         };
 
         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 <code>/proc/*/cmdline</code>.
+            NB: storepass is not supported because it would expose credentials via `/proc/*/cmdline`.
 
-            Specify this or <code>cert</code> and <code>key</code>.
+            Specify this or `cert` and `key`.
           '';
           type = types.nullOr types.str;
           default = null;
         };
 
         cert = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Path to certificate (PEM with certificate chain).
 
-            Not required if <code>keystore</code> is set.
+            Not required if `keystore` is set.
           '';
           type = types.nullOr types.str;
           default = null;
         };
 
         key = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Path to certificate private key (PEM with private key).
 
-            Not required if <code>keystore</code> is set.
+            Not required if `keystore` is set.
           '';
           type = types.nullOr types.str;
           default = null;
         };
 
         cacert = mkOption {
-          description = ''
-            Path to CA bundle file (PEM/X509). Uses system trust store if <code>null</code>.
+          description = lib.mdDoc ''
+            Path to CA bundle file (PEM/X509). Uses system trust store if `null`.
           '';
           type = types.nullOr types.str;
         };
 
         disableAuthentication = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Disable client authentication, no client certificate will be required.
           '';
           type = types.bool;
@@ -84,7 +84,7 @@ let
         };
 
         allowAll = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             If true, allow all clients, do not check client cert subject.
           '';
           type = types.bool;
@@ -92,7 +92,7 @@ let
         };
 
         allowCN = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Allow client if common name appears in the list.
           '';
           type = types.listOf types.str;
@@ -100,7 +100,7 @@ let
         };
 
         allowOU = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Allow client if organizational unit name appears in the list.
           '';
           type = types.listOf types.str;
@@ -108,7 +108,7 @@ let
         };
 
         allowDNS = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Allow client if DNS subject alternative name appears in the list.
           '';
           type = types.listOf types.str;
@@ -116,7 +116,7 @@ let
         };
 
         allowURI = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Allow client if URI subject alternative name appears in the list.
           '';
           type = types.listOf types.str;
@@ -124,13 +124,13 @@ let
         };
 
         extraArguments = mkOption {
-          description = "Extra arguments to pass to <code>ghostunnel server</code>";
+          description = lib.mdDoc "Extra arguments to pass to `ghostunnel server`";
           type = types.separatedString " ";
           default = "";
         };
 
         unsafeTarget = mkOption {
-          description = ''
+          description = lib.mdDoc ''
             If set, does not limit target to localhost, 127.0.0.1, [::1], or UNIX sockets.
 
             This is meant to protect against accidental unencrypted traffic on
@@ -213,17 +213,17 @@ in
 {
 
   options = {
-    services.ghostunnel.enable = mkEnableOption "ghostunnel";
+    services.ghostunnel.enable = mkEnableOption (lib.mdDoc "ghostunnel");
 
     services.ghostunnel.package = mkOption {
-      description = "The ghostunnel package to use.";
+      description = lib.mdDoc "The ghostunnel package to use.";
       type = types.package;
       default = pkgs.ghostunnel;
       defaultText = literalExpression "pkgs.ghostunnel";
     };
 
     services.ghostunnel.servers = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Server mode ghostunnels (TLS listener -> plain TCP/UNIX target)
       '';
       type = types.attrsOf (types.submodule module);
diff --git a/nixos/modules/services/networking/git-daemon.nix b/nixos/modules/services/networking/git-daemon.nix
index 6be72505c216..80b15eedbbd4 100644
--- a/nixos/modules/services/networking/git-daemon.nix
+++ b/nixos/modules/services/networking/git-daemon.nix
@@ -15,7 +15,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Git daemon, which allows public hosting of git repositories
           without any access controls. This is mostly intended for read-only access.
 
@@ -31,7 +31,7 @@ in
         type = types.str;
         default = "";
         example = "/srv/git/";
-        description = ''
+        description = lib.mdDoc ''
           Remap all the path requests as relative to the given path. For example,
           if you set base-path to /srv/git, then if you later try to pull
           git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git.
@@ -41,7 +41,7 @@ in
       exportAll = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Publish all directories that look like Git repositories (have the objects
           and refs subdirectories), even if they do not have the git-daemon-export-ok file.
 
@@ -57,7 +57,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "/srv/git" "/home/user/git/repo2" ];
-        description = ''
+        description = lib.mdDoc ''
           A whitelist of paths of git repositories, or directories containing repositories
           all of which would be published. Paths must not end in "/".
 
@@ -70,31 +70,31 @@ in
         type = types.str;
         default = "";
         example = "example.com";
-        description = "Listen on a specific IP address or hostname.";
+        description = lib.mdDoc "Listen on a specific IP address or hostname.";
       };
 
       port = mkOption {
         type = types.port;
         default = 9418;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       options = mkOption {
         type = types.str;
         default = "";
-        description = "Extra configuration options to be passed to Git daemon.";
+        description = lib.mdDoc "Extra configuration options to be passed to Git daemon.";
       };
 
       user = mkOption {
         type = types.str;
         default = "git";
-        description = "User under which Git daemon would be running.";
+        description = lib.mdDoc "User under which Git daemon would be running.";
       };
 
       group = mkOption {
         type = types.str;
         default = "git";
-        description = "Group under which Git daemon would be running.";
+        description = lib.mdDoc "Group under which Git daemon would be running.";
       };
 
     };
diff --git a/nixos/modules/services/networking/globalprotect-vpn.nix b/nixos/modules/services/networking/globalprotect-vpn.nix
index 976fdf2b962a..36aa93780402 100644
--- a/nixos/modules/services/networking/globalprotect-vpn.nix
+++ b/nixos/modules/services/networking/globalprotect-vpn.nix
@@ -5,7 +5,8 @@ with lib;
 let
   cfg = config.services.globalprotect;
 
-  execStart = if cfg.csdWrapper == null then
+  execStart =
+    if cfg.csdWrapper == null then
       "${pkgs.globalprotect-openconnect}/bin/gpservice"
     else
       "${pkgs.globalprotect-openconnect}/bin/gpservice --csd-wrapper=${cfg.csdWrapper}";
@@ -13,12 +14,26 @@ in
 
 {
   options.services.globalprotect = {
-    enable = mkEnableOption "globalprotect";
+    enable = mkEnableOption (lib.mdDoc "globalprotect");
+
+    settings = mkOption {
+      description = lib.mdDoc ''
+        GlobalProtect-openconnect configuration. For more information, visit
+        <https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration>.
+      '';
+      default = { };
+      example = {
+        "vpn1.company.com" = {
+          openconnect-args = "--script=/path/to/vpnc-script";
+        };
+      };
+      type = types.attrs;
+    };
 
     csdWrapper = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         A script that will produce a Host Integrity Protection (HIP) report,
-        as described at <link xlink:href="https://www.infradead.org/openconnect/hip.html" />
+        as described at <https://www.infradead.org/openconnect/hip.html>
       '';
       default = null;
       example = literalExpression ''"''${pkgs.openconnect}/libexec/openconnect/hipreport.sh"'';
@@ -29,12 +44,14 @@ in
   config = mkIf cfg.enable {
     services.dbus.packages = [ pkgs.globalprotect-openconnect ];
 
+    environment.etc."gpservice/gp.conf".text = lib.generators.toINI { } cfg.settings;
+
     systemd.services.gpservice = {
       description = "GlobalProtect openconnect DBus service";
       serviceConfig = {
-        Type="dbus";
-        BusName="com.yuezk.qt.GPService";
-        ExecStart=execStart;
+        Type = "dbus";
+        BusName = "com.yuezk.qt.GPService";
+        ExecStart = execStart;
       };
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/gnunet.nix b/nixos/modules/services/networking/gnunet.nix
index 5c41967d279b..9d1c9746f728 100644
--- a/nixos/modules/services/networking/gnunet.nix
+++ b/nixos/modules/services/networking/gnunet.nix
@@ -47,7 +47,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run the GNUnet daemon.  GNUnet is GNU's anonymous
           peer-to-peer communication and file sharing framework.
         '';
@@ -57,7 +57,7 @@ in
         quota = mkOption {
           type = types.int;
           default = 1024;
-          description = ''
+          description = lib.mdDoc ''
             Maximum file system usage (in MiB) for file sharing.
           '';
         };
@@ -67,7 +67,7 @@ in
         port = mkOption {
           type = types.port;
           default = 2086;  # assigned by IANA
-          description = ''
+          description = lib.mdDoc ''
             The UDP port for use by GNUnet.
           '';
         };
@@ -77,7 +77,7 @@ in
         port = mkOption {
           type = types.port;
           default = 2086;  # assigned by IANA
-          description = ''
+          description = lib.mdDoc ''
             The TCP port for use by GNUnet.
           '';
         };
@@ -87,7 +87,7 @@ in
         maxNetDownBandwidth = mkOption {
           type = types.int;
           default = 50000;
-          description = ''
+          description = lib.mdDoc ''
             Maximum bandwidth usage (in bits per second) for GNUnet
             when downloading data.
           '';
@@ -96,7 +96,7 @@ in
         maxNetUpBandwidth = mkOption {
           type = types.int;
           default = 50000;
-          description = ''
+          description = lib.mdDoc ''
             Maximum bandwidth usage (in bits per second) for GNUnet
             when downloading data.
           '';
@@ -105,7 +105,7 @@ in
         hardNetUpBandwidth = mkOption {
           type = types.int;
           default = 0;
-          description = ''
+          description = lib.mdDoc ''
             Hard bandwidth limit (in bits per second) when uploading
             data.
           '';
@@ -116,14 +116,14 @@ in
         type = types.package;
         default = pkgs.gnunet;
         defaultText = literalExpression "pkgs.gnunet";
-        description = "Overridable attribute of the gnunet package to use.";
+        description = lib.mdDoc "Overridable attribute of the gnunet package to use.";
         example = literalExpression "pkgs.gnunet_git";
       };
 
       extraOptions = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Additional options that will be copied verbatim in `gnunet.conf'.
           See `gnunet.conf(5)' for details.
         '';
diff --git a/nixos/modules/services/networking/go-autoconfig.nix b/nixos/modules/services/networking/go-autoconfig.nix
new file mode 100644
index 000000000000..07c628ae2cad
--- /dev/null
+++ b/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/nixos/modules/services/networking/go-neb.nix b/nixos/modules/services/networking/go-neb.nix
index 765834fad83e..8c04542c47cc 100644
--- a/nixos/modules/services/networking/go-neb.nix
+++ b/nixos/modules/services/networking/go-neb.nix
@@ -9,11 +9,11 @@ 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;
-      description = "Port (and optionally address) to listen on.";
+      description = lib.mdDoc "Port (and optionally address) to listen on.";
       default = ":4050";
     };
 
@@ -21,25 +21,25 @@ in {
       type = types.nullOr types.path;
       default = null;
       example = "/run/keys/go-neb.env";
-      description = ''
+      description = lib.mdDoc ''
         Environment variables from this file will be interpolated into the
-        final config file using envsubst with this syntax: <literal>$ENVIRONMENT</literal>
-        or <literal>''${VARIABLE}</literal>.
-        The file should contain lines formatted as <literal>SECRET_VAR=SECRET_VALUE</literal>.
+        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.
       '';
     };
 
     baseUrl = mkOption {
       type = types.str;
-      description = "Public-facing endpoint that can receive webhooks.";
+      description = lib.mdDoc "Public-facing endpoint that can receive webhooks.";
     };
 
     config = mkOption {
       inherit (settingsFormat) type;
-      description = ''
-        Your <filename>config.yaml</filename> as a Nix attribute set.
-        See <link xlink:href="https://github.com/matrix-org/go-neb/blob/master/config.sample.yaml">config.sample.yaml</link>
+      description = lib.mdDoc ''
+        Your {file}`config.yaml` as a Nix attribute set.
+        See [config.sample.yaml](https://github.com/matrix-org/go-neb/blob/master/config.sample.yaml)
         for possible options.
       '';
     };
diff --git a/nixos/modules/services/networking/go-shadowsocks2.nix b/nixos/modules/services/networking/go-shadowsocks2.nix
index afbd7ea27c65..d9c4a2421d72 100644
--- a/nixos/modules/services/networking/go-shadowsocks2.nix
+++ b/nixos/modules/services/networking/go-shadowsocks2.nix
@@ -5,11 +5,11 @@ 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;
-      description = "Server listen address or URL";
+      description = lib.mdDoc "Server listen address or URL";
       example = "ss://AEAD_CHACHA20_POLY1305:your-password@:8488";
     };
   };
diff --git a/nixos/modules/services/networking/gobgpd.nix b/nixos/modules/services/networking/gobgpd.nix
index 29ef9a5cf1e3..b22242edaade 100644
--- a/nixos/modules/services/networking/gobgpd.nix
+++ b/nixos/modules/services/networking/gobgpd.nix
@@ -8,14 +8,14 @@ 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;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         GoBGP configuration. Refer to
-        <link xlink:href="https://github.com/osrg/gobgp#documentation"/>
+        <https://github.com/osrg/gobgp#documentation>
         for details on supported values.
       '';
       example = literalExpression ''
diff --git a/nixos/modules/services/networking/gvpe.nix b/nixos/modules/services/networking/gvpe.nix
index 4fad37ba15ee..2279ceee2f58 100644
--- a/nixos/modules/services/networking/gvpe.nix
+++ b/nixos/modules/services/networking/gvpe.nix
@@ -42,12 +42,12 @@ in
 {
   options = {
     services.gvpe = {
-      enable = lib.mkEnableOption "gvpe";
+      enable = lib.mkEnableOption (lib.mdDoc "gvpe");
 
       nodename = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description =''
+        description =lib.mdDoc ''
           GVPE node name
         '';
       };
@@ -68,7 +68,7 @@ in
           on alpha if-up = if-up-0
           on alpha pid-file = /var/gvpe/gvpe.pid
         '';
-        description = ''
+        description = lib.mdDoc ''
           GVPE config contents
         '';
       };
@@ -76,14 +76,14 @@ in
         default = null;
         type = types.nullOr types.path;
         example = "/root/my-gvpe-conf";
-        description = ''
+        description = lib.mdDoc ''
           GVPE config file, if already present
         '';
       };
       ipAddress = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           IP address to assign to GVPE interface
         '';
       };
@@ -91,14 +91,14 @@ in
         default = null;
         type = types.nullOr types.str;
         example = "10.0.0.0/8";
-        description = ''
+        description = lib.mdDoc ''
           IP subnet assigned to GVPE network
         '';
       };
       customIFSetup = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Additional commands to apply in ifup script
         '';
       };
diff --git a/nixos/modules/services/networking/hans.nix b/nixos/modules/services/networking/hans.nix
index 2639b4b68001..3ea95b3bdae9 100644
--- a/nixos/modules/services/networking/hans.nix
+++ b/nixos/modules/services/networking/hans.nix
@@ -19,12 +19,12 @@ in
     services.hans = {
       clients = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Each attribute of this option defines a systemd service that
           runs hans. Many or none may be defined.
           The name of each service is
-          <literal>hans-<replaceable>name</replaceable></literal>
-          where <replaceable>name</replaceable> is the name of the
+          `hans-«name»`
+          where «name» is the name of the
           corresponding attribute name.
         '';
         example = literalExpression ''
@@ -41,21 +41,21 @@ in
             server = mkOption {
               type = types.str;
               default = "";
-              description = "IP address of server running hans";
+              description = lib.mdDoc "IP address of server running hans";
               example = "192.0.2.1";
             };
 
             extraConfig = mkOption {
               type = types.str;
               default = "";
-              description = "Additional command line parameters";
+              description = lib.mdDoc "Additional command line parameters";
               example = "-v";
             };
 
             passwordFile = mkOption {
               type = types.str;
               default = "";
-              description = "File that containts password";
+              description = lib.mdDoc "File that contains password";
             };
 
           };
@@ -66,33 +66,33 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = "enable hans server";
+          description = lib.mdDoc "enable hans server";
         };
 
         ip = mkOption {
           type = types.str;
           default = "";
-          description = "The assigned ip range";
+          description = lib.mdDoc "The assigned ip range";
           example = "198.51.100.0";
         };
 
         respondToSystemPings = mkOption {
           type = types.bool;
           default = false;
-          description = "Force hans respond to ordinary pings";
+          description = lib.mdDoc "Force hans respond to ordinary pings";
         };
 
         extraConfig = mkOption {
           type = types.str;
           default = "";
-          description = "Additional command line parameters";
+          description = lib.mdDoc "Additional command line parameters";
           example = "-v";
         };
 
         passwordFile = mkOption {
           type = types.str;
           default = "";
-          description = "File that containts password";
+          description = lib.mdDoc "File that contains password";
         };
       };
 
diff --git a/nixos/modules/services/networking/haproxy.nix b/nixos/modules/services/networking/haproxy.nix
index e9d72b35499d..e0b686434b6e 100644
--- a/nixos/modules/services/networking/haproxy.nix
+++ b/nixos/modules/services/networking/haproxy.nix
@@ -20,7 +20,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable HAProxy, the reliable, high performance TCP/HTTP
           load balancer.
         '';
@@ -29,21 +29,21 @@ with lib;
       user = mkOption {
         type = types.str;
         default = "haproxy";
-        description = "User account under which haproxy runs.";
+        description = lib.mdDoc "User account under which haproxy runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "haproxy";
-        description = "Group account under which haproxy runs.";
+        description = lib.mdDoc "Group account under which haproxy runs.";
       };
 
       config = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Contents of the HAProxy configuration file,
-          <filename>haproxy.conf</filename>.
+          {file}`haproxy.conf`.
         '';
       };
     };
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 5b07beadb45f..cc46819eed5a 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -1,24 +1,27 @@
-{ 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;
         default = pkgs.headscale;
         defaultText = literalExpression "pkgs.headscale";
-        description = ''
+        description = lib.mdDoc ''
           Which headscale package to use for the running server.
         '';
       };
@@ -26,42 +29,35 @@ 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 = ''
-          The url clients will connect to.
-        '';
-        example = "https://myheadscale.example.com:443";
-      };
-
       address = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           Listening address of headscale.
         '';
         example = "0.0.0.0";
@@ -70,343 +66,352 @@ in
       port = mkOption {
         type = types.port;
         default = 8080;
-        description = ''
+        description = lib.mdDoc ''
           Listening port of headscale.
         '';
         example = 443;
       };
 
-      privateKeyFile = mkOption {
-        type = types.path;
-        default = "${dataDir}/private.key";
-        description = ''
-          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 = ''
-            List of urls containing DERP maps.
-            See <link xlink:href="https://tailscale.com/blog/how-tailscale-works/">How Tailscale works</link> for more information on DERP maps.
-          '';
-        };
-
-        paths = mkOption {
-          type = types.listOf types.path;
-          default = [ ];
-          description = ''
-            List of file paths containing DERP maps.
-            See <link xlink:href="https://tailscale.com/blog/how-tailscale-works/">How Tailscale works</link> for more information on DERP maps.
-          '';
-        };
-
-
-        autoUpdate = mkOption {
-          type = types.bool;
-          default = true;
-          description = ''
-            Whether to automatically update DERP maps on a set frequency.
-          '';
-          example = false;
-        };
-
-        updateFrequency = mkOption {
-          type = types.str;
-          default = "24h";
-          description = ''
-            Frequency to update DERP maps.
-          '';
-          example = "5m";
-        };
-
-      };
-
-      ephemeralNodeInactivityTimeout = mkOption {
-        type = types.str;
-        default = "30m";
-        description = ''
-          Time before an inactive ephemeral node is deleted.
-        '';
-        example = "5m";
-      };
-
-      database = {
-        type = mkOption {
-          type = types.enum [ "sqlite3" "postgres" ];
-          example = "postgres";
-          default = "sqlite3";
-          description = "Database engine to use.";
-        };
-
-        host = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "127.0.0.1";
-          description = "Database host address.";
-        };
-
-        port = mkOption {
-          type = types.nullOr types.port;
-          default = null;
-          example = 3306;
-          description = "Database host port.";
-        };
-
-        name = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "headscale";
-          description = "Database name.";
-        };
-
-        user = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "headscale";
-          description = "Database user.";
-        };
-
-        passwordFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          example = "/run/keys/headscale-dbpassword";
-          description = ''
-            A file containing the password corresponding to
-            <option>database.user</option>.
-          '';
-        };
-
-        path = mkOption {
-          type = types.nullOr types.str;
-          default = "${dataDir}/db.sqlite";
-          description = "Path to the sqlite3 database file.";
-        };
-      };
-
-      logLevel = mkOption {
-        type = types.str;
-        default = "info";
-        description = ''
-          headscale log level.
-        '';
-        example = "debug";
-      };
-
-      dns = {
-        nameservers = mkOption {
-          type = types.listOf types.str;
-          default = [ "1.1.1.1" ];
-          description = ''
-            List of nameservers to pass to Tailscale clients.
-          '';
-        };
-
-        domains = mkOption {
-          type = types.listOf types.str;
-          default = [ ];
-          description = ''
-            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 = ''
-            Defines the base domain to create the hostnames for MagicDNS.
-            <option>baseDomain</option> must be a FQDNs, without the trailing dot.
-            The FQDN of the hosts will be
-            <literal>hostname.namespace.base_domain</literal> (e.g.
-            <literal>myhost.mynamespace.example.com</literal>).
-          '';
-        };
-      };
-
-      openIdConnect = {
-        issuer = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            URL to OpenID issuer.
-          '';
-          example = "https://openid.example.com";
-        };
-
-        clientId = mkOption {
-          type = types.str;
-          default = "";
-          description = ''
-            OpenID Connect client ID.
-          '';
-        };
-
-        clientSecretFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = ''
-            Path to OpenID Connect client secret file.
-          '';
-        };
-
-        domainMap = mkOption {
-          type = types.attrsOf types.str;
-          default = { };
-          description = ''
-            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 = ''
-              Domain name to request a TLS certificate for.
-            '';
-          };
-          challengeType = mkOption {
-            type = types.enum [ "TLS_ALPN-01" "HTTP-01" ];
-            default = "HTTP-01";
-            description = ''
-              Type of ACME challenge to use, currently supported types:
-              <literal>HTTP-01</literal> or <literal>TLS_ALPN-01</literal>.
-            '';
-          };
-          httpListen = mkOption {
-            type = types.nullOr types.str;
-            default = ":http";
-            description = ''
-              When HTTP-01 challenge is chosen, letsencrypt must set up a
-              verification endpoint, and it will be listening on:
-              <literal>:http = port 80</literal>.
-            '';
-          };
-        };
-
-        certFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = ''
-            Path to already created certificate.
-          '';
-        };
-        keyFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = ''
-            Path to key for already created certificate.
-          '';
-        };
-      };
-
-      aclPolicyFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = ''
-          Path to a file containg ACL policies.
-        '';
-      };
-
       settings = mkOption {
-        type = settingsFormat.type;
-        default = { };
-        description = ''
-          Overrides to <filename>config.yaml</filename> as a Nix attribute set.
-          This option is ideal for overriding settings not exposed as Nix options.
-          Check the <link xlink:href="https://github.com/juanfont/headscale/blob/main/config-example.yaml">example config</link>
+        description = lib.mdDoc ''
+          Overrides to {file}`config.yaml` as a Nix attribute set.
+          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_file = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                description = lib.mdDoc ''
+                  Path to OpenID Connect client secret file.
+                '';
+              };
+
+              domain_map = 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.
+              '';
+            };
+
+            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 containg 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_file"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ["services" "headscale" "settings" "oidc" "domain_map"])
+    (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"])
+  ];
+
+  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 +419,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,66 +430,68 @@ 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})"
         ''}
 
-        export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.openIdConnect.clientSecretFile})"
+        ${optionalString (cfg.settings.oidc.client_secret_file != null) ''
+          export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.settings.oidc.client_secret_file})"
+        ''}
         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/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index f719ff59cc7f..63bb44256dd6 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -53,13 +53,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable putting a wireless interface into infrastructure mode,
           allowing other wireless devices to associate with the wireless
           interface and do wireless networking. A simple access point will
-          <option>enable hostapd.wpa</option>,
-          <option>hostapd.wpaPassphrase</option>, and
-          <option>hostapd.ssid</option>, as well as DHCP on the wireless
+          {option}`enable hostapd.wpa`,
+          {option}`hostapd.wpaPassphrase`, and
+          {option}`hostapd.ssid`, as well as DHCP on the wireless
           interface to provide IP addresses to the associated stations, and
           NAT (from the wireless interface to an upstream interface).
         '';
@@ -69,15 +69,15 @@ in
         default = "";
         example = "wlp2s0";
         type = types.str;
-        description = ''
-          The interfaces <command>hostapd</command> will use.
+        description = lib.mdDoc ''
+          The interfaces {command}`hostapd` will use.
         '';
       };
 
       noScan = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Do not scan for overlapping BSSs in HT40+/- mode.
           Caution: turning this on will violate regulatory requirements!
         '';
@@ -87,8 +87,8 @@ in
         default = "nl80211";
         example = "hostapd";
         type = types.str;
-        description = ''
-          Which driver <command>hostapd</command> will use.
+        description = lib.mdDoc ''
+          Which driver {command}`hostapd` will use.
           Most applications will probably use the default.
         '';
       };
@@ -97,13 +97,13 @@ in
         default = "nixos";
         example = "mySpecialSSID";
         type = types.str;
-        description = "SSID to be used in IEEE 802.11 management frames.";
+        description = lib.mdDoc "SSID to be used in IEEE 802.11 management frames.";
       };
 
       hwMode = mkOption {
         default = "g";
         type = types.enum [ "a" "b" "g" ];
-        description = ''
+        description = lib.mdDoc ''
           Operation mode.
           (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g).
         '';
@@ -113,11 +113,11 @@ in
         default = 7;
         example = 11;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Channel number (IEEE 802.11)
           Please note that some drivers do not use this value from
-          <command>hostapd</command> and the channel will need to be configured
-          separately with <command>iwconfig</command>.
+          {command}`hostapd` and the channel will need to be configured
+          separately with {command}`iwconfig`.
         '';
       };
 
@@ -125,15 +125,15 @@ in
         default = "wheel";
         example = "network";
         type = types.str;
-        description = ''
-          Members of this group can control <command>hostapd</command>.
+        description = lib.mdDoc ''
+          Members of this group can control {command}`hostapd`.
         '';
       };
 
       wpa = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point.
         '';
       };
@@ -142,7 +142,7 @@ in
         default = "my_sekret";
         example = "any_64_char_string";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           WPA-PSK (pre-shared-key) passphrase. Clients will need this
           passphrase to associate with this access point.
           Warning: This passphrase will get put into a world-readable file in
@@ -153,7 +153,7 @@ in
       logLevel = mkOption {
         default = 2;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Levels (minimum value for logged events):
           0 = verbose debugging
           1 = debugging
@@ -167,7 +167,7 @@ in
         default = null;
         example = "US";
         type = with types; nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           Country code (ISO/IEC 3166-1). Used to set regulatory domain.
           Set as needed to indicate country in which device is operating.
           This can limit available channels and transmit power.
@@ -187,7 +187,7 @@ in
           ht_capab=[HT40-][SHORT-GI-40][DSSS_CCK-40]
           '';
         type = types.lines;
-        description = "Extra configuration options to put in hostapd.conf.";
+        description = lib.mdDoc "Extra configuration options to put in hostapd.conf.";
       };
     };
   };
@@ -199,7 +199,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/nixos/modules/services/networking/htpdate.nix b/nixos/modules/services/networking/htpdate.nix
index 6954e5b060c4..8b9bb2888dac 100644
--- a/nixos/modules/services/networking/htpdate.nix
+++ b/nixos/modules/services/networking/htpdate.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable htpdate daemon.
         '';
       };
@@ -27,7 +27,7 @@ in
       extraOptions = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Additional command line arguments to pass to htpdate.
         '';
       };
@@ -35,7 +35,7 @@ in
       servers = mkOption {
         type = types.listOf types.str;
         default = [ "www.google.com" ];
-        description = ''
+        description = lib.mdDoc ''
           HTTP servers to use for time synchronization.
         '';
       };
@@ -44,7 +44,7 @@ in
         type = types.str;
         default = "";
         example = "127.0.0.1:8118";
-        description = ''
+        description = lib.mdDoc ''
           HTTP proxy used for requests.
         '';
       };
diff --git a/nixos/modules/services/networking/https-dns-proxy.nix b/nixos/modules/services/networking/https-dns-proxy.nix
index 85d6c362b466..87eb23ea4585 100644
--- a/nixos/modules/services/networking/https-dns-proxy.nix
+++ b/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,50 +46,52 @@ in
   ###### interface
 
   options.services.https-dns-proxy = {
-    enable = mkEnableOption "https-dns-proxy daemon";
+    enable = mkEnableOption (lib.mdDoc "https-dns-proxy daemon");
 
     address = mkOption {
-      description = "The address on which to listen";
+      description = lib.mdDoc "The address on which to listen";
       type = types.str;
       default = "127.0.0.1";
     };
 
     port = mkOption {
-      description = "The port on which to listen";
+      description = lib.mdDoc "The port on which to listen";
       type = types.port;
       default = 5053;
     };
 
     provider = {
       kind = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           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;
       };
 
       ips = mkOption {
-        description = "The custom provider IPs";
+        description = lib.mdDoc "The custom provider IPs";
         type = types.listOf types.str;
       };
 
       url = mkOption {
-        description = "The custom provider URL";
+        description = lib.mdDoc "The custom provider URL";
         type = types.str;
       };
     };
 
     preferIPv4 = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         https_dns_proxy will by default use IPv6 and fail if it is not available.
         To play it safe, we choose IPv4.
       '';
@@ -94,7 +100,7 @@ in
     };
 
     extraArgs = mkOption {
-      description = "Additional arguments to pass to the process.";
+      description = lib.mdDoc "Additional arguments to pass to the process.";
       type = types.listOf types.str;
       default = [ "-v" ];
     };
@@ -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/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix
index 8f621b61002f..82c144236f3b 100644
--- a/nixos/modules/services/networking/hylafax/options.nix
+++ b/nixos/modules/services/networking/hylafax/options.nix
@@ -37,17 +37,17 @@ let
       name = mkOption {
         type = nonEmptyStr;
         example = "ttyS1";
-        description = ''
+        description = lib.mdDoc ''
           Name of modem device,
-          will be searched for in <filename>/dev</filename>.
+          will be searched for in {file}`/dev`.
         '';
       };
       type = mkOption {
         type = nonEmptyStr;
         example = "cirrus";
-        description = ''
+        description = lib.mdDoc ''
           Name of modem configuration file,
-          will be searched for in <filename>config</filename>
+          will be searched for in {file}`config`
           in the spooling area directory.
         '';
       };
@@ -59,11 +59,11 @@ let
           FAXNumber = "123456";
           LocalIdentifier = "LostInBerlin";
         };
-        description = ''
+        description = lib.mdDoc ''
           Attribute set of values for the given modem.
           ${commonDescr}
           Options defined here override options in
-          <option>commonModemConfig</option> for this modem.
+          {option}`commonModemConfig` for this modem.
         '';
       };
     };
@@ -118,15 +118,15 @@ in
 
   options.services.hylafax = {
 
-    enable = mkEnableOption "HylaFAX server";
+    enable = mkEnableOption (lib.mdDoc "HylaFAX server");
 
     autostart = mkOption {
       type = bool;
       default = true;
       example = false;
-      description = ''
+      description = lib.mdDoc ''
         Autostart the HylaFAX queue manager at system start.
-        If this is <literal>false</literal>, the queue manager
+        If this is `false`, the queue manager
         will still be started if there are pending
         jobs or if a user tries to connect to it.
       '';
@@ -136,34 +136,34 @@ in
       type = nullOr nonEmptyStr;
       default = null;
       example = "49";
-      description = "Country code for server and all modems.";
+      description = lib.mdDoc "Country code for server and all modems.";
     };
 
     areaCode = mkOption {
       type = nullOr nonEmptyStr;
       default = null;
       example = "30";
-      description = "Area code for server and all modems.";
+      description = lib.mdDoc "Area code for server and all modems.";
     };
 
     longDistancePrefix = mkOption {
       type = nullOr str;
       default = null;
       example = "0";
-      description = "Long distance prefix for server and all modems.";
+      description = lib.mdDoc "Long distance prefix for server and all modems.";
     };
 
     internationalPrefix = mkOption {
       type = nullOr str;
       default = null;
       example = "00";
-      description = "International prefix for server and all modems.";
+      description = lib.mdDoc "International prefix for server and all modems.";
     };
 
     spoolAreaPath = mkOption {
       type = path;
       default = "/var/spool/fax";
-      description = ''
+      description = lib.mdDoc ''
         The spooling area will be created/maintained
         at the location given here.
       '';
@@ -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.
       '';
     };
@@ -197,11 +197,11 @@ in
       type = path;
       example = literalExpression ''"''${pkgs.postfix}/bin/sendmail"'';
       # '' ;  # fix vim
-      description = ''
-        Path to <filename>sendmail</filename> program.
+      description = lib.mdDoc ''
+        Path to {file}`sendmail` program.
         The default uses the local sendmail wrapper
-        (see <option>config.services.mail.sendmailSetuidWrapper</option>),
-        otherwise the <filename>false</filename>
+        (see {option}`config.services.mail.sendmailSetuidWrapper`),
+        otherwise the {file}`false`
         binary to cause an error if used.
       '';
     };
@@ -209,9 +209,9 @@ in
     hfaxdConfig = mkOption {
       type = configAttrType;
       example.RecvqProtection = "0400";
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of lines for the global
-        hfaxd config file <filename>etc/hfaxd.conf</filename>.
+        hfaxd config file {file}`etc/hfaxd.conf`.
         ${commonDescr}
       '';
     };
@@ -222,9 +222,9 @@ in
         InternationalPrefix = "00";
         LongDistancePrefix = "0";
       };
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of lines for the global
-        faxq config file <filename>etc/config</filename>.
+        faxq config file {file}`etc/config`.
         ${commonDescr}
       '';
     };
@@ -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.
@@ -254,7 +254,7 @@ in
           LocalIdentifier = "Smith";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         Description of installed modems.
         At least on modem must be defined
         to enable the HylaFAX server.
@@ -265,31 +265,31 @@ in
       type = lines;
       default = "";
       example = "chmod 0755 .  # everyone may read my faxes";
-      description = ''
+      description = lib.mdDoc ''
         Additional shell code that is executed within the
         spooling area directory right after its setup.
       '';
     };
 
-    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;
       example = "daily";
-      description = ''
+      description = lib.mdDoc ''
         Purge old files from the spooling area with
-        <filename>faxcron</filename> with the given frequency
+        {file}`faxcron` with the given frequency
         (see systemd.time(7)).
       '';
     };
     faxcron.infoDays = mkOption {
       type = ints.positive;
       default = 30;
-      description = ''
+      description = lib.mdDoc ''
         Set the expiration time for data in the
         remote machine information directory in days.
       '';
@@ -297,7 +297,7 @@ in
     faxcron.logDays = mkOption {
       type = ints.positive;
       default = 30;
-      description = ''
+      description = lib.mdDoc ''
         Set the expiration time for
         session trace log files in days.
       '';
@@ -305,24 +305,24 @@ in
     faxcron.rcvDays = mkOption {
       type = ints.positive;
       default = 7;
-      description = ''
+      description = lib.mdDoc ''
         Set the expiration time for files in
         the received facsimile queue in days.
       '';
     };
 
-    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;
       example = "daily";
-      description = ''
+      description = lib.mdDoc ''
         Purge old files from the spooling area with
-        <filename>faxcron</filename> with the given frequency
+        {file}`faxcron` with the given frequency
         (see systemd.time(7)).
       '';
     };
@@ -330,12 +330,12 @@ in
       type = enum [ "never" "as-flagged" "always" ];
       default = "as-flagged";
       example = "always";
-      description = ''
+      description = lib.mdDoc ''
         Enable or suppress job archiving:
-        <literal>never</literal> disables job archiving,
-        <literal>as-flagged</literal> archives jobs that
+        `never` disables job archiving,
+        `as-flagged` archives jobs that
         have been flagged for archiving by sendfax,
-        <literal>always</literal> forces archiving of all jobs.
+        `always` forces archiving of all jobs.
         See also sendfax(1) and faxqclean(8).
       '';
     };
@@ -343,7 +343,7 @@ in
       type = ints.positive;
       default = 15;
       example = literalExpression "24*60";
-      description = ''
+      description = lib.mdDoc ''
         Set the job
         age threshold (in minutes) that controls how long
         jobs may reside in the doneq directory.
@@ -353,7 +353,7 @@ in
       type = ints.positive;
       default = 60;
       example = literalExpression "24*60";
-      description = ''
+      description = lib.mdDoc ''
         Set the document
         age threshold (in minutes) that controls how long
         unreferenced files may reside in the docq directory.
diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix
index 4506bbbc5eb7..df6d0f49eec4 100644
--- a/nixos/modules/services/networking/hylafax/systemd.nix
+++ b/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/nixos/modules/services/networking/i2p.nix b/nixos/modules/services/networking/i2p.nix
index 3b6010531f13..c5c7a955cbd4 100644
--- a/nixos/modules/services/networking/i2p.nix
+++ b/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/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 34fda57b23d2..3f6cb97296b5 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -17,36 +17,36 @@ 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;
-      description = "The endpoint name.";
+      description = lib.mdDoc "The endpoint name.";
     };
     address = mkOption {
       type = types.str;
       default = addr;
-      description = "Bind address for ${name} endpoint.";
+      description = lib.mdDoc "Bind address for ${name} endpoint.";
     };
     port = mkOption {
       type = types.port;
       default = port;
-      description = "Bind port for ${name} endpoint.";
+      description = lib.mdDoc "Bind port for ${name} endpoint.";
     };
   };
 
   i2cpOpts = name: {
     length = mkOption {
       type = types.int;
-      description = "Guaranteed minimum hops for ${name} tunnels.";
+      description = lib.mdDoc "Guaranteed minimum hops for ${name} tunnels.";
       default = 3;
     };
     quantity = mkOption {
       type = types.int;
-      description = "Number of simultaneous ${name} tunnels.";
+      description = lib.mdDoc "Number of simultaneous ${name} tunnels.";
       default = 5;
     };
   };
@@ -56,7 +56,7 @@ let
       keys = mkOption {
         type = with types; nullOr str;
         default = keyloc;
-        description = ''
+        description = lib.mdDoc ''
           File to persist ${lib.toUpper name} keys.
         '';
       };
@@ -64,12 +64,12 @@ let
       outbound = i2cpOpts name;
       latency.min = mkOption {
         type = with types; nullOr int;
-        description = "Min latency for tunnels.";
+        description = lib.mdDoc "Min latency for tunnels.";
         default = null;
       };
       latency.max = mkOption {
         type = with types; nullOr int;
-        description = "Max latency for tunnels.";
+        description = lib.mdDoc "Max latency for tunnels.";
         default = null;
       };
     };
@@ -79,17 +79,17 @@ let
     inbound = i2cpOpts name;
     crypto.tagsToSend = mkOption {
       type = types.int;
-      description = "Number of ElGamal/AES tags to send.";
+      description = lib.mdDoc "Number of ElGamal/AES tags to send.";
       default = 40;
     };
     destination = mkOption {
       type = types.str;
-      description = "Remote endpoint, I2P hostname or b32.i2p address.";
+      description = lib.mdDoc "Remote endpoint, I2P hostname or b32.i2p address.";
     };
     keys = mkOption {
       type = types.str;
       default = name + "-keys.dat";
-      description = "Keyset used for tunnel identity.";
+      description = lib.mdDoc "Keyset used for tunnel identity.";
     };
   } // mkEndpointOpt name "127.0.0.1" 0;
 
@@ -158,6 +158,10 @@ let
       (sec "addressbook")
       (strOpt "defaulturl" cfg.addressbook.defaulturl)
     ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
+      ++ [
+      (sec "meshnets")
+      (boolOpt "yggdrasil" cfg.yggdrasil.enable)
+    ] ++ (optionalNullString "yggaddress" cfg.yggdrasil.address)
       ++ (flip map
       (collect (proto: proto ? port && proto ? address) cfg.proto)
       (proto: let protoOpts = [
@@ -243,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.
@@ -255,7 +259,7 @@ in
         type = types.package;
         default = pkgs.i2pd;
         defaultText = literalExpression "pkgs.i2pd";
-        description = ''
+        description = lib.mdDoc ''
           i2pd package to use.
         '';
       };
@@ -263,21 +267,21 @@ in
       logLevel = mkOption {
         type = types.enum ["debug" "info" "warn" "error"];
         default = "error";
-        description = ''
-          The log level. <command>i2pd</command> defaults to "info"
+        description = lib.mdDoc ''
+          The log level. {command}`i2pd` defaults to "info"
           but that generates copious amounts of log messages.
 
           We default to "error" which is similar to the default log
-          level of <command>tor</command>.
+          level of {command}`tor`.
         '';
       };
 
-      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;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Your external IP or hostname.
         '';
       };
@@ -285,7 +289,7 @@ in
       family = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specify a family the router belongs to.
         '';
       };
@@ -293,7 +297,7 @@ in
       dataDir = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
         '';
       };
@@ -301,7 +305,7 @@ in
       share = mkOption {
         type = types.int;
         default = 100;
-        description = ''
+        description = lib.mdDoc ''
           Limit of transit traffic from max bandwidth in percents.
         '';
       };
@@ -309,7 +313,7 @@ in
       ifname = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Network interface to bind to.
         '';
       };
@@ -317,7 +321,7 @@ in
       ifname4 = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           IPv4 interface to bind to.
         '';
       };
@@ -325,7 +329,7 @@ in
       ifname6 = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           IPv6 interface to bind to.
         '';
       };
@@ -333,7 +337,7 @@ in
       ntcpProxy = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Proxy URL for NTCP transport.
         '';
       };
@@ -341,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.
         '';
       };
@@ -356,7 +360,7 @@ in
       netid = mkOption {
         type = types.int;
         default = 2;
-        description = ''
+        description = lib.mdDoc ''
           I2P overlay netid.
         '';
       };
@@ -364,50 +368,50 @@ in
       bandwidth = mkOption {
         type = with types; nullOr int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
            Set a router bandwidth limit integer in KBps.
-           If not set, <command>i2pd</command> defaults to 32KBps.
+           If not set, {command}`i2pd` defaults to 32KBps.
         '';
       };
 
       port = mkOption {
         type = with types; nullOr int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           I2P listen port. If no one is given the router will pick between 9111 and 30777.
         '';
       };
 
       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";
-        description = ''
+        description = lib.mdDoc ''
           Name i2pd appears in UPnP forwardings list.
         '';
       };
 
       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;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Full path to SU3 file to reseed from.
         '';
       };
@@ -415,7 +419,7 @@ in
       reseed.urls = mkOption {
         type = with types; listOf str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Reseed URLs.
         '';
       };
@@ -423,7 +427,7 @@ in
       reseed.floodfill = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to router info of floodfill to reseed from.
         '';
       };
@@ -431,7 +435,7 @@ in
       reseed.zipfile = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to local .zip file to reseed from.
         '';
       };
@@ -439,7 +443,7 @@ in
       reseed.proxy = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           URL for reseed proxy, supports http/socks.
         '';
       };
@@ -447,7 +451,7 @@ in
      addressbook.defaulturl = mkOption {
         type = types.str;
         default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
-        description = ''
+        description = lib.mdDoc ''
           AddressBook subscription URL for initial setup
         '';
       };
@@ -458,30 +462,30 @@ in
           "http://i2p-projekt.i2p/hosts.txt"
           "http://stats.i2p/cgi-bin/newhosts.txt"
         ];
-        description = ''
+        description = lib.mdDoc ''
           AddressBook subscription URLs
         '';
       };
 
-      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 = ''
-          Router Familiy to trust for first hops.
+        description = lib.mdDoc ''
+          Router Family to trust for first hops.
         '';
       };
 
       trust.routers = mkOption {
         type = with types; listOf str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Only connect to the listed routers.
         '';
       };
 
-      trust.hidden = mkEnableOption "Router concealment";
+      trust.hidden = mkEnableOption (lib.mdDoc "Router concealment");
 
       websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
 
@@ -489,11 +493,11 @@ 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 = ''
+        description = lib.mdDoc ''
           Port to listen for incoming NTCP2 connections (0=auto).
         '';
       };
@@ -501,7 +505,7 @@ in
       limits.transittunnels = mkOption {
         type = types.int;
         default = 2500;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of active transit sessions.
         '';
       };
@@ -509,7 +513,7 @@ in
       limits.coreSize = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Maximum size of corefile in Kb (0 - use system limit).
         '';
       };
@@ -517,7 +521,7 @@ in
       limits.openFiles = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of open files (0 - use system default).
         '';
       };
@@ -525,7 +529,7 @@ in
       limits.ntcpHard = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of active transit sessions.
         '';
       };
@@ -533,7 +537,7 @@ in
       limits.ntcpSoft = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
         '';
       };
@@ -541,19 +545,30 @@ in
       limits.ntcpThreads = mkOption {
         type = types.int;
         default = 1;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of threads used by NTCP DH worker.
         '';
       };
 
+      yggdrasil.enable = mkEnableOption (lib.mdDoc "Yggdrasil");
+
+      yggdrasil.address = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = lib.mdDoc ''
+          Your local yggdrasil address. Specify it if you want to bind your router to a
+          particular address.
+        '';
+      };
+
       proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
 
-        auth = mkEnableOption "Webconsole authentication";
+        auth = mkEnableOption (lib.mdDoc "Webconsole authentication");
 
         user = mkOption {
           type = types.str;
           default = "i2pd";
-          description = ''
+          description = lib.mdDoc ''
             Username for webconsole access
           '';
         };
@@ -561,7 +576,7 @@ in
         pass = mkOption {
           type = types.str;
           default = "i2pd";
-          description = ''
+          description = lib.mdDoc ''
             Password for webconsole access.
           '';
         };
@@ -569,7 +584,7 @@ in
         strictHeaders = mkOption {
           type = with types; nullOr bool;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Enable strict host checking on WebUI.
           '';
         };
@@ -577,7 +592,7 @@ in
         hostname = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Expected hostname for WebUI.
           '';
         };
@@ -588,21 +603,21 @@ in
         outproxy = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = "Upstream outproxy bind address.";
+          description = lib.mdDoc "Upstream outproxy bind address.";
         };
       };
       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";
-          description = "Upstream outproxy bind address.";
+          description = lib.mdDoc "Upstream outproxy bind address.";
         };
         outproxyPort = mkOption {
           type = types.int;
           default = 4444;
-          description = "Upstream outproxy bind port.";
+          description = lib.mdDoc "Upstream outproxy bind port.";
         };
       };
 
@@ -619,7 +634,7 @@ in
               destinationPort = mkOption {
                 type = with types; nullOr int;
                 default = null;
-                description = "Connect to particular port at destination.";
+                description = lib.mdDoc "Connect to particular port at destination.";
               };
             } // commonTunOpts name;
             config = {
@@ -627,7 +642,7 @@ in
             };
           }
         ));
-        description = ''
+        description = lib.mdDoc ''
           Connect to someone as a client and establish a local accept endpoint
         '';
       };
@@ -640,12 +655,12 @@ in
               inPort = mkOption {
                 type = types.int;
                 default = 0;
-                description = "Service port. Default to the tunnel's listen port.";
+                description = lib.mdDoc "Service port. Default to the tunnel's listen port.";
               };
               accessList = mkOption {
                 type = with types; listOf str;
                 default = [];
-                description = "I2P nodes that are allowed to connect to this service.";
+                description = lib.mdDoc "I2P nodes that are allowed to connect to this service.";
               };
             } // commonTunOpts name;
             config = {
@@ -653,7 +668,7 @@ in
             };
           }
         ));
-        description = ''
+        description = lib.mdDoc ''
           Serve something on I2P network at port and delegate requests to address inPort.
         '';
       };
diff --git a/nixos/modules/services/networking/icecream/daemon.nix b/nixos/modules/services/networking/icecream/daemon.nix
index 8593c94e34dc..fdd7a139c2fa 100644
--- a/nixos/modules/services/networking/icecream/daemon.nix
+++ b/nixos/modules/services/networking/icecream/daemon.nix
@@ -12,18 +12,18 @@ in {
 
     services.icecream.daemon = {
 
-     enable = mkEnableOption "Icecream Daemon";
+     enable = mkEnableOption (lib.mdDoc "Icecream Daemon");
 
       openFirewall = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to automatically open receive port in the firewall.
         '';
       };
 
       openBroadcast = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to automatically open the firewall for scheduler discovery.
         '';
       };
@@ -31,7 +31,7 @@ in {
       cacheLimit = mkOption {
         type = types.ints.u16;
         default = 256;
-        description = ''
+        description = lib.mdDoc ''
           Maximum size in Megabytes of cache used to store compile environments of compile clients.
         '';
       };
@@ -39,7 +39,7 @@ in {
       netName = mkOption {
         type = types.str;
         default = "ICECREAM";
-        description = ''
+        description = lib.mdDoc ''
           Network name to connect to. A scheduler with the same name needs to be running.
         '';
       };
@@ -47,7 +47,7 @@ in {
       noRemote = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Prevent jobs from other nodes being scheduled on this daemon.
         '';
       };
@@ -55,7 +55,7 @@ in {
       schedulerHost = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Explicit scheduler hostname, useful in firewalled environments.
 
           Uses scheduler autodiscovery via broadcast if set to null.
@@ -65,7 +65,7 @@ in {
       maxProcesses = mkOption {
         type = types.nullOr types.ints.u16;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of compile jobs started in parallel for this daemon.
 
           Uses the number of CPUs if set to null.
@@ -75,7 +75,7 @@ in {
       nice = mkOption {
         type = types.int;
         default = 5;
-        description = ''
+        description = lib.mdDoc ''
           The level of niceness to use.
         '';
       };
@@ -83,7 +83,7 @@ in {
       hostname = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Hostname of the daemon in the icecream infrastructure.
 
           Uses the hostname retrieved via uname if set to null.
@@ -93,7 +93,7 @@ in {
       user = mkOption {
         type = types.str;
         default = "icecc";
-        description = ''
+        description = lib.mdDoc ''
           User to run the icecream daemon as. Set to root to enable receive of
           remote compile environments.
         '';
@@ -103,13 +103,13 @@ in {
         default = pkgs.icecream;
         defaultText = literalExpression "pkgs.icecream";
         type = types.package;
-        description = "Icecream package to use.";
+        description = lib.mdDoc "Icecream package to use.";
       };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Additional command line parameters.";
+        description = lib.mdDoc "Additional command line parameters.";
         example = [ "-v" ];
       };
     };
diff --git a/nixos/modules/services/networking/icecream/scheduler.nix b/nixos/modules/services/networking/icecream/scheduler.nix
index 14fbc966b989..33aee1bb19cc 100644
--- a/nixos/modules/services/networking/icecream/scheduler.nix
+++ b/nixos/modules/services/networking/icecream/scheduler.nix
@@ -11,12 +11,12 @@ in {
   options = {
 
     services.icecream.scheduler = {
-      enable = mkEnableOption "Icecream Scheduler";
+      enable = mkEnableOption (lib.mdDoc "Icecream Scheduler");
 
       netName = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Network name for the icecream scheduler.
 
           Uses the default ICECREAM if null.
@@ -26,14 +26,14 @@ in {
       port = mkOption {
         type = types.port;
         default = 8765;
-        description = ''
+        description = lib.mdDoc ''
           Server port to listen for icecream daemon requests.
         '';
       };
 
       openFirewall = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to automatically open the daemon port in the firewall.
         '';
       };
@@ -41,7 +41,7 @@ in {
       openTelnet = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to open the telnet TCP port on 8766.
         '';
       };
@@ -49,7 +49,7 @@ in {
       persistentClientConnection = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to prevent clients from connecting to a better scheduler.
         '';
       };
@@ -58,13 +58,13 @@ in {
         default = pkgs.icecream;
         defaultText = literalExpression "pkgs.icecream";
         type = types.package;
-        description = "Icecream package to use.";
+        description = lib.mdDoc "Icecream package to use.";
       };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Additional command line parameters";
+        description = lib.mdDoc "Additional command line parameters";
         example = [ "-v" ];
       };
     };
diff --git a/nixos/modules/services/networking/inspircd.nix b/nixos/modules/services/networking/inspircd.nix
index 81c367ec8f7d..da193df105b7 100644
--- a/nixos/modules/services/networking/inspircd.nix
+++ b/nixos/modules/services/networking/inspircd.nix
@@ -12,17 +12,17 @@ in {
 
   options = {
     services.inspircd = {
-      enable = lib.mkEnableOption "InspIRCd";
+      enable = lib.mkEnableOption (lib.mdDoc "InspIRCd");
 
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.inspircd;
         defaultText = lib.literalExpression "pkgs.inspircd";
         example = lib.literalExpression "pkgs.inspircdMinimal";
-        description = ''
+        description = lib.mdDoc ''
           The InspIRCd package to use. This is mainly useful
           to specify an overridden version of the
-          <literal>pkgs.inspircd</literal> dervivation, for
+          `pkgs.inspircd` dervivation, for
           example if you want to use a more minimal InspIRCd
           distribution with less modules enabled or with
           modules enabled which can't be distributed in binary
@@ -32,13 +32,13 @@ in {
 
       config = lib.mkOption {
         type = lib.types.lines;
-        description = ''
-          Verbatim <literal>inspircd.conf</literal> file.
+        description = lib.mdDoc ''
+          Verbatim `inspircd.conf` file.
           For a list of options, consult the
-          <link xlink:href="https://docs.inspircd.org/3/configuration/">InspIRCd documentation</link>, the
-          <link xlink:href="https://docs.inspircd.org/3/modules/">Module documentation</link>
+          [InspIRCd documentation](https://docs.inspircd.org/3/configuration/), the
+          [Module documentation](https://docs.inspircd.org/3/modules/)
           and the example configuration files distributed
-          with <literal>pkgs.inspircd.doc</literal>
+          with `pkgs.inspircd.doc`
         '';
       };
     };
diff --git a/nixos/modules/services/networking/iodine.nix b/nixos/modules/services/networking/iodine.nix
index e241afe3269b..ea2fa3ac4be4 100644
--- a/nixos/modules/services/networking/iodine.nix
+++ b/nixos/modules/services/networking/iodine.nix
@@ -28,12 +28,12 @@ in
     services.iodine = {
       clients = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Each attribute of this option defines a systemd service that
           runs iodine. Many or none may be defined.
           The name of each service is
-          <literal>iodine-<replaceable>name</replaceable></literal>
-          where <replaceable>name</replaceable> is the name of the
+          `iodine-«name»`
+          where «name» is the name of the
           corresponding attribute name.
         '';
         example = literalExpression ''
@@ -52,28 +52,28 @@ in
                 server = mkOption {
                   type = types.str;
                   default = "";
-                  description = "Hostname of server running iodined";
+                  description = lib.mdDoc "Hostname of server running iodined";
                   example = "tunnel.mydomain.com";
                 };
 
                 relay = mkOption {
                   type = types.str;
                   default = "";
-                  description = "DNS server to use as an intermediate relay to the iodined server";
+                  description = lib.mdDoc "DNS server to use as an intermediate relay to the iodined server";
                   example = "8.8.8.8";
                 };
 
                 extraConfig = mkOption {
                   type = types.str;
                   default = "";
-                  description = "Additional command line parameters";
+                  description = lib.mdDoc "Additional command line parameters";
                   example = "-l 192.168.1.10 -p 23";
                 };
 
                 passwordFile = mkOption {
                   type = types.str;
                   default = "";
-                  description = "Path to a file containing the password.";
+                  description = lib.mdDoc "Path to a file containing the password.";
                 };
               };
             }
@@ -85,34 +85,34 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = "enable iodined server";
+          description = lib.mdDoc "enable iodined server";
         };
 
         ip = mkOption {
           type = types.str;
           default = "";
-          description = "The assigned ip address or ip range";
+          description = lib.mdDoc "The assigned ip address or ip range";
           example = "172.16.10.1/24";
         };
 
         domain = mkOption {
           type = types.str;
           default = "";
-          description = "Domain or subdomain of which nameservers point to us";
+          description = lib.mdDoc "Domain or subdomain of which nameservers point to us";
           example = "tunnel.mydomain.com";
         };
 
         extraConfig = mkOption {
           type = types.str;
           default = "";
-          description = "Additional command line parameters";
+          description = lib.mdDoc "Additional command line parameters";
           example = "-l 192.168.1.10 -p 23";
         };
 
         passwordFile = mkOption {
           type = types.str;
           default = "";
-          description = "File that contains password";
+          description = lib.mdDoc "File that contains password";
         };
       };
 
diff --git a/nixos/modules/services/networking/iperf3.nix b/nixos/modules/services/networking/iperf3.nix
index 0fe378b225d7..0a204524e00f 100644
--- a/nixos/modules/services/networking/iperf3.nix
+++ b/nixos/modules/services/networking/iperf3.nix
@@ -3,56 +3,56 @@ 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 = "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;
       default     = null;
-      description = "CPU affinity for the process.";
+      description = lib.mdDoc "CPU affinity for the process.";
     };
     bind = mkOption {
       type        = types.nullOr types.str;
       default     = null;
-      description = "Bind to the specific interface associated with the given address.";
+      description = lib.mdDoc "Bind to the specific interface associated with the given address.";
     };
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = "Open ports in the firewall for iperf3.";
+      description = lib.mdDoc "Open ports in the firewall for iperf3.";
     };
     verbose = mkOption {
       type        = types.bool;
       default     = false;
-      description = "Give more detailed output.";
+      description = lib.mdDoc "Give more detailed output.";
     };
     forceFlush = mkOption {
       type        = types.bool;
       default     = false;
-      description = "Force flushing output at every interval.";
+      description = lib.mdDoc "Force flushing output at every interval.";
     };
     debug = mkOption {
       type        = types.bool;
       default     = false;
-      description = "Emit debugging output.";
+      description = lib.mdDoc "Emit debugging output.";
     };
     rsaPrivateKey = mkOption {
       type        = types.nullOr types.path;
       default     = null;
-      description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client.";
+      description = lib.mdDoc "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client.";
     };
     authorizedUsersFile = mkOption {
       type        = types.nullOr types.path;
       default     = null;
-      description = "Path to the configuration file containing authorized users credentials to run iperf tests.";
+      description = lib.mdDoc "Path to the configuration file containing authorized users credentials to run iperf tests.";
     };
     extraFlags = mkOption {
       type        = types.listOf types.str;
       default     = [ ];
-      description = "Extra flags to pass to iperf3(1).";
+      description = lib.mdDoc "Extra flags to pass to iperf3(1).";
     };
   };
 
diff --git a/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixos/modules/services/networking/ircd-hybrid/default.nix
index f659f3f3e8c1..554b0f7bb8b4 100644
--- a/nixos/modules/services/networking/ircd-hybrid/default.nix
+++ b/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/nixos/modules/services/networking/ircd-hybrid/ircd.conf b/nixos/modules/services/networking/ircd-hybrid/ircd.conf
index 17ef203840af..b82094cf5f09 100644
--- a/nixos/modules/services/networking/ircd-hybrid/ircd.conf
+++ b/nixos/modules/services/networking/ircd-hybrid/ircd.conf
@@ -98,7 +98,7 @@ serverinfo {
 	 * 
 	 * 	openssl genrsa -out rsa.key 2048
 	 *	openssl rsa -in rsa.key -pubout -out rsa.pub
-	 *	chown <ircd-user>.<ircd.group> rsa.key rsa.pub
+	 *	chown <ircd-user>:<ircd.group> rsa.key rsa.pub
 	 *	chmod 0600 rsa.key
 	 *	chmod 0644 rsa.pub
 	 */
diff --git a/nixos/modules/services/networking/iscsi/initiator.nix b/nixos/modules/services/networking/iscsi/initiator.nix
index 051c9c7bff3c..d2865a660ead 100644
--- a/nixos/modules/services/networking/iscsi/initiator.nix
+++ b/nixos/modules/services/networking/iscsi/initiator.nix
@@ -4,24 +4,24 @@ 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;
-      description = "Portal to discover targets on";
+      description = lib.mdDoc "Portal to discover targets on";
     };
     name = mkOption {
       type = str;
-      description = "Name of this iscsi initiator";
+      description = lib.mdDoc "Name of this iscsi initiator";
       example = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
     };
     package = mkOption {
       type = package;
-      description = "openiscsi package to use";
+      description = lib.mdDoc "openiscsi package to use";
       default = pkgs.openiscsi;
       defaultText = literalExpression "pkgs.openiscsi";
     };
@@ -29,11 +29,11 @@ in
     extraConfig = mkOption {
       type = str;
       default = "";
-      description = "Lines to append to default iscsid.conf";
+      description = lib.mdDoc "Lines to append to default iscsid.conf";
     };
 
     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.
       '';
diff --git a/nixos/modules/services/networking/iscsi/root-initiator.nix b/nixos/modules/services/networking/iscsi/root-initiator.nix
index c12aca1bc24d..4434fedce1eb 100644
--- a/nixos/modules/services/networking/iscsi/root-initiator.nix
+++ b/nixos/modules/services/networking/iscsi/root-initiator.nix
@@ -19,7 +19,7 @@ in
   # machines to be up.
   options.boot.iscsi-initiator = with types; {
     name = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Name of the iSCSI initiator to boot from. Note, booting from iscsi
         requires networkd based networking.
       '';
@@ -29,7 +29,7 @@ in
     };
 
     discoverPortal = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         iSCSI portal to boot from.
       '';
       default = null;
@@ -38,7 +38,7 @@ in
     };
 
     target = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Name of the iSCSI target to boot from.
       '';
       default = null;
@@ -47,7 +47,7 @@ in
     };
 
     logLevel = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Higher numbers elicits more logs.
       '';
       default = 1;
@@ -56,7 +56,7 @@ in
     };
 
     loginAll = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Do not log into a specific target on the portal, but to all that we discover.
         This overrides setting target.
       '';
@@ -65,19 +65,19 @@ in
     };
 
     extraIscsiCommands = mkOption {
-      description = "Extra iscsi commands to run in the initrd.";
+      description = lib.mdDoc "Extra iscsi commands to run in the initrd.";
       default = "";
       type = lines;
     };
 
     extraConfig = mkOption {
-      description = "Extra lines to append to /etc/iscsid.conf";
+      description = lib.mdDoc "Extra lines to append to /etc/iscsid.conf";
       default = null;
       type = nullOr lines;
     };
 
     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`.
diff --git a/nixos/modules/services/networking/iscsi/target.nix b/nixos/modules/services/networking/iscsi/target.nix
index 8a10e7d346ae..88eaf4590030 100644
--- a/nixos/modules/services/networking/iscsi/target.nix
+++ b/nixos/modules/services/networking/iscsi/target.nix
@@ -9,12 +9,12 @@ 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;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Content of /etc/target/saveconfig.json
           This file is normally read and written by targetcli
         '';
diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix
index 5c1480e7e2fb..993a603c1ed5 100644
--- a/nixos/modules/services/networking/iwd.nix
+++ b/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;
@@ -32,9 +41,9 @@ in
         };
       };
 
-      description = ''
+      description = lib.mdDoc ''
         Options passed to iwd.
-        See <link xlink:href="https://iwd.wiki.kernel.org/networkconfigurationsettings">here</link> for supported options.
+        See [here](https://iwd.wiki.kernel.org/networkconfigurationsettings) for supported options.
       '';
     };
   };
@@ -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/nixos/modules/services/networking/jibri/default.nix b/nixos/modules/services/networking/jibri/default.nix
index 113a7aa4384a..a931831fc281 100644
--- a/nixos/modules/services/networking/jibri/default.nix
+++ b/nixos/modules/services/networking/jibri/default.nix
@@ -89,13 +89,13 @@ 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 = { };
-      description = ''
+      description = lib.mdDoc ''
         Jibri configuration.
-        See <link xlink:href="https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf" />
+        See <https://github.com/jitsi/jibri/blob/master/src/main/resources/reference.conf>
         for default configuration with comments.
       '';
     };
@@ -136,7 +136,7 @@ in
         exit 0
         '''''';
       '';
-      description = ''
+      description = lib.mdDoc ''
         This script runs when jibri finishes recording a video of a conference.
       '';
     };
@@ -145,14 +145,14 @@ in
       type = bool;
       default = false;
       example = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the flag "--ignore-certificate-errors" for the Chromium browser opened by Jibri.
         Intended for use in automated tests or anywhere else where using a verified cert for Jitsi-Meet is not possible.
       '';
     };
 
     xmppEnvironments = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         XMPP servers to connect to.
       '';
       example = literalExpression ''
@@ -189,54 +189,54 @@ in
           xmppServerHosts = mkOption {
             type = listOf str;
             example = [ "xmpp.example.org" ];
-            description = ''
+            description = lib.mdDoc ''
               Hostnames of the XMPP servers to connect to.
             '';
           };
           xmppDomain = mkOption {
             type = str;
             example = "xmpp.example.org";
-            description = ''
+            description = lib.mdDoc ''
               The base XMPP domain.
             '';
           };
           control.muc.domain = mkOption {
             type = str;
-            description = ''
+            description = lib.mdDoc ''
               The domain part of the MUC to connect to for control.
             '';
           };
           control.muc.roomName = mkOption {
             type = str;
             default = "JibriBrewery";
-            description = ''
+            description = lib.mdDoc ''
               The room name of the MUC to connect to for control.
             '';
           };
           control.muc.nickname = mkOption {
             type = str;
             default = "jibri";
-            description = ''
+            description = lib.mdDoc ''
               The nickname for this Jibri instance in the MUC.
             '';
           };
           control.login.domain = mkOption {
             type = str;
-            description = ''
+            description = lib.mdDoc ''
               The domain part of the JID for this Jibri instance.
             '';
           };
           control.login.username = mkOption {
             type = str;
             default = "jvb";
-            description = ''
+            description = lib.mdDoc ''
               User part of the JID.
             '';
           };
           control.login.passwordFile = mkOption {
             type = str;
             example = "/run/keys/jibri-xmpp1";
-            description = ''
+            description = lib.mdDoc ''
               File containing the password for the user.
             '';
           };
@@ -244,28 +244,28 @@ in
           call.login.domain = mkOption {
             type = str;
             example = "recorder.xmpp.example.org";
-            description = ''
+            description = lib.mdDoc ''
               The domain part of the JID for the recorder.
             '';
           };
           call.login.username = mkOption {
             type = str;
             default = "recorder";
-            description = ''
+            description = lib.mdDoc ''
               User part of the JID for the recorder.
             '';
           };
           call.login.passwordFile = mkOption {
             type = str;
             example = "/run/keys/jibri-recorder-xmpp1";
-            description = ''
+            description = lib.mdDoc ''
               File containing the password for the user.
             '';
           };
           disableCertificateVerification = mkOption {
             type = bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether to skip validation of the server's certificate.
             '';
           };
@@ -274,7 +274,7 @@ in
             type = str;
             default = "0";
             example = "conference.";
-            description = ''
+            description = lib.mdDoc ''
               The prefix to strip from the room's JID domain to derive the call URL.
             '';
           };
@@ -282,7 +282,7 @@ in
             type = str;
             default = "0";
             example = "1 hour";
-            description = ''
+            description = lib.mdDoc ''
               The duration that the Jibri session can be.
               A value of zero means indefinitely.
             '';
@@ -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/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix
index 647119b9039e..5e9788960736 100644
--- a/nixos/modules/services/networking/jicofo.nix
+++ b/nixos/modules/services/networking/jicofo.nix
@@ -7,12 +7,12 @@ let
 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;
       example = "localhost";
-      description = ''
+      description = lib.mdDoc ''
         Hostname of the XMPP server to connect to.
       '';
     };
@@ -20,17 +20,17 @@ in
     xmppDomain = mkOption {
       type = nullOr str;
       example = "meet.example.org";
-      description = ''
+      description = lib.mdDoc ''
         Domain name of the XMMP server to which to connect as a component.
 
-        If null, <option>xmppHost</option> is used.
+        If null, {option}`xmppHost` is used.
       '';
     };
 
     componentPasswordFile = mkOption {
       type = str;
       example = "/run/keys/jicofo-component";
-      description = ''
+      description = lib.mdDoc ''
         Path to file containing component secret.
       '';
     };
@@ -38,7 +38,7 @@ in
     userName = mkOption {
       type = str;
       default = "focus";
-      description = ''
+      description = lib.mdDoc ''
         User part of the JID for XMPP user connection.
       '';
     };
@@ -46,7 +46,7 @@ in
     userDomain = mkOption {
       type = str;
       example = "auth.meet.example.org";
-      description = ''
+      description = lib.mdDoc ''
         Domain part of the JID for XMPP user connection.
       '';
     };
@@ -54,7 +54,7 @@ in
     userPasswordFile = mkOption {
       type = str;
       example = "/run/keys/jicofo-user";
-      description = ''
+      description = lib.mdDoc ''
         Path to file containing password for XMPP user connection.
       '';
     };
@@ -62,7 +62,7 @@ in
     bridgeMuc = mkOption {
       type = str;
       example = "jvbbrewery@internal.meet.example.org";
-      description = ''
+      description = lib.mdDoc ''
         JID of the internal MUC used to communicate with Videobridges.
       '';
     };
@@ -75,8 +75,8 @@ in
           "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com";
         }
       '';
-      description = ''
-        Contents of the <filename>sip-communicator.properties</filename> configuration file for jicofo.
+      description = lib.mdDoc ''
+        Contents of the {file}`sip-communicator.properties` configuration file for jicofo.
       '';
     };
   };
diff --git a/nixos/modules/services/networking/jitsi-videobridge.nix b/nixos/modules/services/networking/jitsi-videobridge.nix
index abb0bd0a25e1..09f2ddf92c5c 100644
--- a/nixos/modules/services/networking/jitsi-videobridge.nix
+++ b/nixos/modules/services/networking/jitsi-videobridge.nix
@@ -51,7 +51,7 @@ let
 in
 {
   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;
@@ -67,19 +67,19 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Videobridge configuration.
 
-        See <link xlink:href="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.
       '';
     };
 
     xmppConfigs = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         XMPP servers to connect to.
 
-        See <link xlink:href="https://github.com/jitsi/jitsi-videobridge/blob/master/doc/muc.md" /> for more information.
+        See <https://github.com/jitsi/jitsi-videobridge/blob/master/doc/muc.md> for more information.
       '';
       default = { };
       example = literalExpression ''
@@ -98,7 +98,7 @@ in
           hostName = mkOption {
             type = str;
             example = "xmpp.example.org";
-            description = ''
+            description = lib.mdDoc ''
               Hostname of the XMPP server to connect to. Name of the attribute set is used by default.
             '';
           };
@@ -106,35 +106,35 @@ in
             type = nullOr str;
             default = null;
             example = "auth.xmpp.example.org";
-            description = ''
+            description = lib.mdDoc ''
               Domain part of JID of the XMPP user, if it is different from hostName.
             '';
           };
           userName = mkOption {
             type = str;
             default = "jvb";
-            description = ''
+            description = lib.mdDoc ''
               User part of the JID.
             '';
           };
           passwordFile = mkOption {
             type = str;
             example = "/run/keys/jitsi-videobridge-xmpp1";
-            description = ''
+            description = lib.mdDoc ''
               File containing the password for the user.
             '';
           };
           mucJids = mkOption {
             type = str;
             example = "jvbbrewery@internal.xmpp.example.org";
-            description = ''
+            description = lib.mdDoc ''
               JID of the MUC to join. JiCoFo needs to be configured to join the same MUC.
             '';
           };
           mucNickname = mkOption {
             # Upstream DEBs use UUID, let's use hostname instead.
             type = str;
-            description = ''
+            description = lib.mdDoc ''
               Videobridges use the same XMPP account and need to be distinguished by the
               nickname (aka resource part of the JID). By default, system hostname is used.
             '';
@@ -142,7 +142,7 @@ in
           disableCertificateVerification = mkOption {
             type = bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether to skip validation of the server's certificate.
             '';
           };
@@ -150,7 +150,7 @@ in
         config = {
           hostName = mkDefault name;
           mucNickname = mkDefault (builtins.replaceStrings [ "." ] [ "-" ] (
-            config.networking.hostName + optionalString (config.networking.domain != null) ".${config.networking.domain}"
+            config.networking.fqdnOrHostName
           ));
         };
       }));
@@ -161,7 +161,7 @@ in
         type = nullOr str;
         default = null;
         example = "192.168.1.42";
-        description = ''
+        description = lib.mdDoc ''
           Local address when running behind NAT.
         '';
       };
@@ -170,7 +170,7 @@ in
         type = nullOr str;
         default = null;
         example = "1.2.3.4";
-        description = ''
+        description = lib.mdDoc ''
           Public address when running behind NAT.
         '';
       };
@@ -179,7 +179,7 @@ in
     extraProperties = mkOption {
       type = attrsOf str;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Additional Java properties passed to jitsi-videobridge.
       '';
     };
@@ -187,14 +187,14 @@ in
     openFirewall = mkOption {
       type = bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to open ports in the firewall for the videobridge.
       '';
     };
 
     apis = mkOption {
       type = with types; listOf str;
-      description = ''
+      description = lib.mdDoc ''
         What is passed as --apis= parameter. If this is empty, "none" is passed.
         Needed for monitoring jitsi.
       '';
diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix
index 994c511bdc2d..945f4113bd47 100644
--- a/nixos/modules/services/networking/kea.nix
+++ b/nixos/modules/services/networking/kea.nix
@@ -35,38 +35,38 @@ in
 {
   options.services.kea = with types; {
     ctrl-agent = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kea Control Agent configuration
       '';
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea Control Agent";
+          enable = mkEnableOption (lib.mdDoc "Kea Control Agent");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
-            description = ''
-              List of additonal arguments to pass to the daemon.
+            description = lib.mdDoc ''
+              List of additional arguments to pass to the daemon.
             '';
           };
 
           configFile = mkOption {
             type = nullOr path;
             default = null;
-            description = ''
-              Kea Control Agent configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"/>.
+            description = lib.mdDoc ''
+              Kea Control Agent configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html>.
 
-              Takes preference over <link linkend="opt-services.kea.ctrl-agent.settings">settings</link>.
-              Most users should prefer using <link linkend="opt-services.kea.ctrl-agent.settings">settings</link> instead.
+              Takes preference over [settings](#opt-services.kea.ctrl-agent.settings).
+              Most users should prefer using [settings](#opt-services.kea.ctrl-agent.settings) instead.
             '';
           };
 
           settings = mkOption {
             type = format.type;
             default = null;
-            description = ''
-              Kea Control Agent configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"/>.
+            description = lib.mdDoc ''
+              Kea Control Agent configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html>.
             '';
           };
         };
@@ -74,30 +74,30 @@ in
     };
 
     dhcp4 = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         DHCP4 Server configuration
       '';
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea DHCP4 server";
+          enable = mkEnableOption (lib.mdDoc "Kea DHCP4 server");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
-            description = ''
-              List of additonal arguments to pass to the daemon.
+            description = lib.mdDoc ''
+              List of additional arguments to pass to the daemon.
             '';
           };
 
           configFile = mkOption {
             type = nullOr path;
             default = null;
-            description = ''
-              Kea DHCP4 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"/>.
+            description = lib.mdDoc ''
+              Kea DHCP4 configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html>.
 
-              Takes preference over <link linkend="opt-services.kea.dhcp4.settings">settings</link>.
-              Most users should prefer using <link linkend="opt-services.kea.dhcp4.settings">settings</link> instead.
+              Takes preference over [settings](#opt-services.kea.dhcp4.settings).
+              Most users should prefer using [settings](#opt-services.kea.dhcp4.settings) instead.
             '';
           };
 
@@ -125,8 +125,8 @@ in
                 } ];
               } ];
             };
-            description = ''
-              Kea DHCP4 configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"/>.
+            description = lib.mdDoc ''
+              Kea DHCP4 configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html>.
             '';
           };
         };
@@ -134,30 +134,30 @@ in
     };
 
     dhcp6 = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         DHCP6 Server configuration
       '';
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea DHCP6 server";
+          enable = mkEnableOption (lib.mdDoc "Kea DHCP6 server");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
-            description = ''
-              List of additonal arguments to pass to the daemon.
+            description = lib.mdDoc ''
+              List of additional arguments to pass to the daemon.
             '';
           };
 
           configFile = mkOption {
             type = nullOr path;
             default = null;
-            description = ''
-              Kea DHCP6 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"/>.
+            description = lib.mdDoc ''
+              Kea DHCP6 configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html>.
 
-              Takes preference over <link linkend="opt-services.kea.dhcp6.settings">settings</link>.
-              Most users should prefer using <link linkend="opt-services.kea.dhcp6.settings">settings</link> instead.
+              Takes preference over [settings](#opt-services.kea.dhcp6.settings).
+              Most users should prefer using [settings](#opt-services.kea.dhcp6.settings) instead.
             '';
           };
 
@@ -186,8 +186,8 @@ in
                 } ];
               } ];
             };
-            description = ''
-              Kea DHCP6 configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"/>.
+            description = lib.mdDoc ''
+              Kea DHCP6 configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html>.
             '';
           };
         };
@@ -195,30 +195,30 @@ in
     };
 
     dhcp-ddns = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Kea DHCP-DDNS configuration
       '';
       default = {};
       type = submodule {
         options = {
-          enable = mkEnableOption "Kea DDNS server";
+          enable = mkEnableOption (lib.mdDoc "Kea DDNS server");
 
           extraArgs = mkOption {
             type = listOf str;
             default = [];
-            description = ''
-              List of additonal arguments to pass to the daemon.
+            description = lib.mdDoc ''
+              List of additional arguments to pass to the daemon.
             '';
           };
 
           configFile = mkOption {
             type = nullOr path;
             default = null;
-            description = ''
-              Kea DHCP-DDNS configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"/>.
+            description = lib.mdDoc ''
+              Kea DHCP-DDNS configuration as a path, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html>.
 
-              Takes preference over <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link>.
-              Most users should prefer using <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link> instead.
+              Takes preference over [settings](#opt-services.kea.dhcp-ddns.settings).
+              Most users should prefer using [settings](#opt-services.kea.dhcp-ddns.settings) instead.
             '';
           };
 
@@ -239,8 +239,8 @@ in
                 ddns-domains = [ ];
               };
             };
-            description = ''
-              Kea DHCP-DDNS configuration as an attribute set, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"/>.
+            description = lib.mdDoc ''
+              Kea DHCP-DDNS configuration as an attribute set, see <https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html>.
             '';
           };
         };
@@ -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/nixos/modules/services/networking/keepalived/default.nix b/nixos/modules/services/networking/keepalived/default.nix
index c9ac2ee25990..29fbea5545c3 100644
--- a/nixos/modules/services/networking/keepalived/default.nix
+++ b/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 == "");
 
@@ -147,7 +145,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Keepalived.
         '';
       };
@@ -155,7 +153,7 @@ in
       enableScriptSecurity = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Don't run scripts configured to be run as root if any part of the path is writable by a non-root user.
         '';
       };
@@ -165,7 +163,7 @@ in
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable the builtin AgentX subagent.
           '';
         };
@@ -173,7 +171,7 @@ in
         socket = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Socket to use for connecting to SNMP master agent. If this value is
             set to null, keepalived's default will be used, which is
             unix:/var/agentx/master, unless using a network namespace, when the
@@ -184,7 +182,7 @@ in
         enableKeepalived = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable SNMP handling of vrrp element of KEEPALIVED MIB.
           '';
         };
@@ -192,7 +190,7 @@ in
         enableChecker = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable SNMP handling of checker element of KEEPALIVED MIB.
           '';
         };
@@ -200,7 +198,7 @@ in
         enableRfc = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable SNMP handling of RFC2787 and RFC6527 VRRP MIBs.
           '';
         };
@@ -208,7 +206,7 @@ in
         enableRfcV2 = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable SNMP handling of RFC2787 VRRP MIB.
           '';
         };
@@ -216,7 +214,7 @@ in
         enableRfcV3 = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable SNMP handling of RFC6527 VRRP MIB.
           '';
         };
@@ -224,7 +222,7 @@ in
         enableTraps = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable SNMP traps.
           '';
         };
@@ -236,7 +234,7 @@ in
           inherit lib;
         }));
         default = {};
-        description = "Declarative vrrp script config";
+        description = lib.mdDoc "Declarative vrrp script config";
       };
 
       vrrpInstances = mkOption {
@@ -244,13 +242,13 @@ in
           inherit lib;
         }));
         default = {};
-        description = "Declarative vhost config";
+        description = lib.mdDoc "Declarative vhost config";
       };
 
       extraGlobalDefs = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the 'global_defs' block of the
           configuration file
         '';
@@ -259,11 +257,24 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to the configuration file.
         '';
       };
 
+      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/nixos/modules/services/networking/keepalived/virtual-ip-options.nix b/nixos/modules/services/networking/keepalived/virtual-ip-options.nix
index 1b8889b1b472..1fa6a0ee3bf4 100644
--- a/nixos/modules/services/networking/keepalived/virtual-ip-options.nix
+++ b/nixos/modules/services/networking/keepalived/virtual-ip-options.nix
@@ -6,7 +6,7 @@ with lib;
 
     addr = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         IP address, optionally with a netmask: IPADDR[/MASK]
       '';
     };
@@ -14,7 +14,7 @@ with lib;
     brd = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The broadcast address on the interface.
       '';
     };
@@ -22,7 +22,7 @@ with lib;
     dev = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The name of the device to add the address to.
       '';
     };
@@ -30,7 +30,7 @@ with lib;
     scope = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The scope of the area where this address is valid.
       '';
     };
@@ -38,7 +38,7 @@ with lib;
     label = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Each address may be tagged with a label string. In order to preserve
         compatibility with Linux-2.0 net aliases, this string must coincide with
         the name of the device or must be prefixed with the device name followed
diff --git a/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix b/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
index e96dde5fa89f..20e5558d7829 100644
--- a/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
+++ b/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
@@ -6,7 +6,7 @@ with lib;
 
     interface = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Interface for inside_network, bound by vrrp.
       '';
     };
@@ -14,7 +14,7 @@ with lib;
     state = mkOption {
       type = types.enum [ "MASTER" "BACKUP" ];
       default = "BACKUP";
-      description = ''
+      description = lib.mdDoc ''
         Initial state. As soon as the other machine(s) come up, an election will
         be held and the machine with the highest "priority" will become MASTER.
         So the entry here doesn't matter a whole lot.
@@ -23,7 +23,7 @@ with lib;
 
     virtualRouterId = mkOption {
       type = types.int;
-      description = ''
+      description = lib.mdDoc ''
         Arbitrary unique number 0..255. Used to differentiate multiple instances
         of vrrpd running on the same NIC (and hence same socket).
       '';
@@ -32,7 +32,7 @@ with lib;
     priority = mkOption {
       type = types.int;
       default = 100;
-      description = ''
+      description = lib.mdDoc ''
         For electing MASTER, highest priority wins. To be MASTER, make 50 more
         than other machines.
       '';
@@ -41,7 +41,7 @@ with lib;
     noPreempt = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         VRRP will normally preempt a lower priority machine when a higher
         priority machine comes online. "nopreempt" allows the lower priority
         machine to maintain the master role, even when a higher priority machine
@@ -53,7 +53,7 @@ with lib;
     useVmac = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Use VRRP Virtual MAC.
       '';
     };
@@ -61,7 +61,7 @@ with lib;
     vmacInterface = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
          Name of the vmac interface to use. keepalived will come up with a name
          if you don't specify one.
       '';
@@ -70,7 +70,7 @@ with lib;
     vmacXmitBase = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Send/Recv VRRP messages from base interface instead of VMAC interface.
       '';
     };
@@ -78,7 +78,7 @@ with lib;
     unicastSrcIp = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
          Default IP for binding vrrpd is the primary IP on interface. If you
          want to hide location of vrrpd, use this IP as src_addr for unicast
          vrrp packets.
@@ -88,7 +88,7 @@ with lib;
     unicastPeers = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Do not send VRRP adverts over VRRP multicast group. Instead it sends
         adverts to the following list of ip addresses using unicast design
         fashion. It can be cool to use VRRP FSM and features in a networking
@@ -103,27 +103,27 @@ with lib;
       }));
       default = [];
       # TODO: example
-      description = "Declarative vhost config";
+      description = lib.mdDoc "Declarative vhost config";
     };
 
     trackScripts = mkOption {
       type = types.listOf types.str;
       default = [];
       example = [ "chk_cmd1" "chk_cmd2" ];
-      description = "List of script names to invoke for health tracking.";
+      description = lib.mdDoc "List of script names to invoke for health tracking.";
     };
 
     trackInterfaces = mkOption {
       type = types.listOf types.str;
       default = [];
       example = [ "eth0" "eth1" ];
-      description = "List of network interfaces to monitor for health tracking.";
+      description = lib.mdDoc "List of network interfaces to monitor for health tracking.";
     };
 
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra lines to be added verbatim to the vrrp_instance section.
       '';
     };
diff --git a/nixos/modules/services/networking/keepalived/vrrp-script-options.nix b/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
index df7a89cff8cd..852d6b0ec26f 100644
--- a/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
+++ b/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
@@ -8,55 +8,55 @@ with lib.types;
     script = mkOption {
       type = str;
       example = literalExpression ''"''${pkgs.curl} -f http://localhost:80"'';
-      description = "(Path of) Script command to execute followed by args, i.e. cmd [args]...";
+      description = lib.mdDoc "(Path of) Script command to execute followed by args, i.e. cmd [args]...";
     };
 
     interval = mkOption {
       type = int;
       default = 1;
-      description = "Seconds between script invocations.";
+      description = lib.mdDoc "Seconds between script invocations.";
     };
 
     timeout = mkOption {
       type = int;
       default = 5;
-      description = "Seconds after which script is considered to have failed.";
+      description = lib.mdDoc "Seconds after which script is considered to have failed.";
     };
 
     weight = mkOption {
       type = int;
       default = 0;
-      description = "Following a failure, adjust the priority by this weight.";
+      description = lib.mdDoc "Following a failure, adjust the priority by this weight.";
     };
 
     rise = mkOption {
       type = int;
       default = 5;
-      description = "Required number of successes for OK transition.";
+      description = lib.mdDoc "Required number of successes for OK transition.";
     };
 
     fall = mkOption {
       type = int;
       default = 3;
-      description = "Required number of failures for KO transition.";
+      description = lib.mdDoc "Required number of failures for KO transition.";
     };
 
     user = mkOption {
       type = str;
       default = "keepalived_script";
-      description = "Name of user to run the script under.";
+      description = lib.mdDoc "Name of user to run the script under.";
     };
 
     group = mkOption {
       type = nullOr str;
       default = null;
-      description = "Name of group to run the script under. Defaults to user group.";
+      description = lib.mdDoc "Name of group to run the script under. Defaults to user group.";
     };
 
     extraConfig = mkOption {
       type = lines;
       default = "";
-      description = "Extra lines to be added verbatim to the vrrp_script section.";
+      description = lib.mdDoc "Extra lines to be added verbatim to the vrrp_script section.";
     };
 
   };
diff --git a/nixos/modules/services/networking/keybase.nix b/nixos/modules/services/networking/keybase.nix
index 495102cb7eee..ae10aebb86e2 100644
--- a/nixos/modules/services/networking/keybase.nix
+++ b/nixos/modules/services/networking/keybase.nix
@@ -14,7 +14,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to start the Keybase service.";
+        description = lib.mdDoc "Whether to start the Keybase service.";
       };
 
     };
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
index a58a03997b3b..e97195d82919 100644
--- a/nixos/modules/services/networking/knot.nix
+++ b/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,20 +37,20 @@ 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 = ''
-          List of additional command line paramters for knotd
+        description = lib.mdDoc ''
+          List of additional command line parameters for knotd
         '';
       };
 
       keyFiles = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of files containing additional configuration
           to be included using the include directive. This option
           allows to include configuration like TSIG keys without
@@ -63,7 +63,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to knot.conf
         '';
       };
@@ -72,7 +72,7 @@ in {
         type = types.package;
         default = pkgs.knot-dns;
         defaultText = literalExpression "pkgs.knot-dns";
-        description = ''
+        description = lib.mdDoc ''
           Which Knot DNS package to use
         '';
       };
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index 28b8be7a9a0d..55af6abd5e01 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -50,18 +50,18 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable knot-resolver domain name server.
         DNSSEC validation is turned on by default.
-        You can run <literal>sudo nc -U /run/knot-resolver/control/1</literal>
+        You can run `sudo nc -U /run/knot-resolver/control/1`
         and give commands interactively to kresd@1.service.
       '';
     };
     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; }";
@@ -69,7 +69,7 @@ in {
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra lines to be added verbatim to the generated configuration file.
       '';
     };
@@ -77,7 +77,7 @@ in {
       type = with types; listOf str;
       default = [ "[::1]:53" "127.0.0.1:53" ];
       example = [ "53" ];
-      description = ''
+      description = lib.mdDoc ''
         What addresses and ports the server should listen on.
         For detailed syntax see ListenStream in man systemd.socket.
       '';
@@ -86,7 +86,7 @@ in {
       type = with types; listOf str;
       default = [];
       example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ];
-      description = ''
+      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.
       '';
@@ -95,7 +95,7 @@ in {
       type = with types; listOf str;
       default = [];
       example = [ "198.51.100.1:443" "[2001:db8::1]:443" "443" ];
-      description = ''
+      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.
       '';
@@ -103,7 +103,7 @@ in {
     instances = mkOption {
       type = types.ints.unsigned;
       default = 1;
-      description = ''
+      description = lib.mdDoc ''
         The number of instances to start.  They will be called kresd@{1,2,...}.service.
         Knot Resolver uses no threads, so this is the way to scale.
         You can dynamically start/stop them at will, so this is just system default.
diff --git a/nixos/modules/services/networking/lambdabot.nix b/nixos/modules/services/networking/lambdabot.nix
index 3005e5824554..8609bc971962 100644
--- a/nixos/modules/services/networking/lambdabot.nix
+++ b/nixos/modules/services/networking/lambdabot.nix
@@ -21,20 +21,20 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable the Lambdabot IRC bot";
+        description = lib.mdDoc "Enable the Lambdabot IRC bot";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.lambdabot;
         defaultText = literalExpression "pkgs.lambdabot";
-        description = "Used lambdabot package";
+        description = lib.mdDoc "Used lambdabot package";
       };
 
       script = mkOption {
         type = types.str;
         default = "";
-        description = "Lambdabot script";
+        description = lib.mdDoc "Lambdabot script";
       };
 
     };
diff --git a/nixos/modules/services/networking/libreswan.nix b/nixos/modules/services/networking/libreswan.nix
index 429167aed9d7..785729d8f742 100644
--- a/nixos/modules/services/networking/libreswan.nix
+++ b/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;
@@ -60,7 +60,7 @@ in
             protostack=netkey
             virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10
         '';
-        description = "Options to go in the 'config setup' section of the Libreswan IPsec configuration";
+        description = lib.mdDoc "Options to go in the 'config setup' section of the Libreswan IPsec configuration";
       };
 
       connections = mkOption {
@@ -79,7 +79,7 @@ in
             ''';
           }
         '';
-        description = "A set of connections to define for the Libreswan IPsec service";
+        description = lib.mdDoc "A set of connections to define for the Libreswan IPsec service";
       };
 
       policies = mkOption {
@@ -93,22 +93,22 @@ 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.
+          :::
         '';
       };
 
       disableRedirects = mkOption {
         type = types.bool;
         default = true;
-        description = ''
-          Whether to disable send and accept redirects for all nework interfaces.
-          See the Libreswan <link xlink:href="https://libreswan.org/wiki/FAQ#Why_is_it_recommended_to_disable_send_redirects_in_.2Fproc.2Fsys.2Fnet_.3F">
-          FAQ</link> page for why this is recommended.
+        description = lib.mdDoc ''
+          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/nixos/modules/services/networking/lldpd.nix b/nixos/modules/services/networking/lldpd.nix
index d5de9c45d84b..b7ac99d75d75 100644
--- a/nixos/modules/services/networking/lldpd.nix
+++ b/nixos/modules/services/networking/lldpd.nix
@@ -9,13 +9,13 @@ 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;
       default = [];
       example = [ "-c" "-k" "-I eth0" ];
-      description = "List of command line parameters for lldpd";
+      description = lib.mdDoc "List of command line parameters for lldpd";
     };
   };
 
diff --git a/nixos/modules/services/networking/logmein-hamachi.nix b/nixos/modules/services/networking/logmein-hamachi.nix
index 11cbdda2f845..7c00b82e3b34 100644
--- a/nixos/modules/services/networking/logmein-hamachi.nix
+++ b/nixos/modules/services/networking/logmein-hamachi.nix
@@ -18,7 +18,7 @@ in
       type = types.bool;
       default = false;
       description =
-        ''
+        lib.mdDoc ''
           Whether to enable LogMeIn Hamachi, a proprietary
           (closed source) commercial VPN software.
         '';
diff --git a/nixos/modules/services/networking/lokinet.nix b/nixos/modules/services/networking/lokinet.nix
new file mode 100644
index 000000000000..f6bc314ed260
--- /dev/null
+++ b/nixos/modules/services/networking/lokinet.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.lokinet;
+  dataDir = "/var/lib/lokinet";
+  settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; };
+  configFile = settingsFormat.generate "lokinet.ini" (lib.filterAttrsRecursive (n: v: v != null) cfg.settings);
+in with lib; {
+  options.services.lokinet = {
+    enable = mkEnableOption (lib.mdDoc "Lokinet daemon");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.lokinet;
+      defaultText = literalExpression "pkgs.lokinet";
+      description = lib.mdDoc "Lokinet package to use.";
+    };
+
+    useLocally = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = lib.mdDoc "Whether to use Lokinet locally.";
+    };
+
+    settings = mkOption {
+      type = with types;
+        submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            dns = {
+              bind = mkOption {
+                type = str;
+                default = "127.3.2.1";
+                description = lib.mdDoc "Address to bind to for handling DNS requests.";
+              };
+
+              upstream = mkOption {
+                type = listOf str;
+                default = [ "9.9.9.10" ];
+                example = [ "1.1.1.1" "8.8.8.8" ];
+                description = lib.mdDoc ''
+                  Upstream resolver(s) to use as fallback for non-loki addresses.
+                  Multiple values accepted.
+                '';
+              };
+            };
+
+            network = {
+              exit = mkOption {
+                type = bool;
+                default = false;
+                description = lib.mdDoc ''
+                  Whether to act as an exit node. Beware that this
+                  increases demand on the server and may pose liability concerns.
+                  Enable at your own risk.
+                '';
+              };
+
+              exit-node = mkOption {
+                type = nullOr (listOf str);
+                default = null;
+                example = ''
+                  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 = lib.mdDoc ''
+                  Specify a `.loki` address and an optional ip range to use as an exit broker.
+                  See <http://probably.loki/wiki/index.php?title=Exit_Nodes> for
+                  a list of exit nodes.
+                '';
+              };
+
+              keyfile = mkOption {
+                type = nullOr str;
+                default = null;
+                example = "snappkey.private";
+                description = lib.mdDoc ''
+                  The private key to persist address with. If not specified the address will be ephemeral.
+                  This keyfile is generated automatically if the specified file doesn't exist.
+                '';
+              };
+            };
+          };
+        };
+      default = { };
+      example = literalExpression ''
+        {
+          dns = {
+            bind = "127.3.2.1";
+            upstream = [ "1.1.1.1" "8.8.8.8" ];
+          };
+
+          network.exit-node = [ "example.loki" "example2.loki" ];
+        }
+      '';
+      description = lib.mdDoc ''
+        Configuration for Lokinet.
+        Currently, the best way to view the available settings is by
+        generating a config file using `lokinet -g`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.resolvconf.extraConfig = mkIf cfg.useLocally ''
+      name_servers="${cfg.settings.dns.bind}"
+    '';
+
+    systemd.services.lokinet = {
+      description = "Lokinet";
+      after = [ "network-online.target" "network.target" ];
+      wants = [ "network-online.target" "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        ln -sf ${cfg.package}/share/bootstrap.signed ${dataDir}
+        ${pkgs.coreutils}/bin/install -m 600 ${configFile} ${dataDir}/lokinet.ini
+
+        ${optionalString (cfg.settings.network.keyfile != null) ''
+          ${pkgs.crudini}/bin/crudini --set ${dataDir}/lokinet.ini network keyfile "${dataDir}/${cfg.settings.network.keyfile}"
+        ''}
+      '';
+
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "lokinet";
+        AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" ];
+        ExecStart = "${cfg.package}/bin/lokinet ${dataDir}/lokinet.ini";
+        Restart = "always";
+        RestartSec = "5s";
+
+        # hardening
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        PrivateMounts = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "strict";
+        ReadWritePaths = "/dev/net/tun";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/services/networking/lxd-image-server.nix b/nixos/modules/services/networking/lxd-image-server.nix
index d326626eed44..d8e32eb997e8 100644
--- a/nixos/modules/services/networking/lxd-image-server.nix
+++ b/nixos/modules/services/networking/lxd-image-server.nix
@@ -11,30 +11,30 @@ in
 {
   options = {
     services.lxd-image-server = {
-      enable = mkEnableOption "lxd-image-server";
+      enable = mkEnableOption (lib.mdDoc "lxd-image-server");
 
       group = mkOption {
         type = types.str;
-        description = "Group assigned to the user and the webroot directory.";
+        description = lib.mdDoc "Group assigned to the user and the webroot directory.";
         default = "nginx";
         example = "www-data";
       };
 
       settings = mkOption {
         type = format.type;
-        description = ''
+        description = lib.mdDoc ''
           Configuration for lxd-image-server.
 
-          Example see <link xlink:href="https://github.com/Avature/lxd-image-server/blob/master/config.toml"/>.
+          Example see <https://github.com/Avature/lxd-image-server/blob/master/config.toml>.
         '';
         default = {};
       };
 
       nginx = {
-        enable = mkEnableOption "nginx";
+        enable = mkEnableOption (lib.mdDoc "nginx");
         domain = mkOption {
           type = types.str;
-          description = "Domain to use for nginx virtual host.";
+          description = lib.mdDoc "Domain to use for nginx virtual host.";
           example = "images.example.org";
         };
       };
@@ -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/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
index 09d357cd2b6e..9dd1f62350af 100644
--- a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
+++ b/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/nixos/modules/services/networking/matterbridge.nix b/nixos/modules/services/networking/matterbridge.nix
index 9186eee26abf..2921074fcd2b 100644
--- a/nixos/modules/services/networking/matterbridge.nix
+++ b/nixos/modules/services/networking/matterbridge.nix
@@ -17,13 +17,13 @@ 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;
         default = null;
         example = "/etc/nixos/matterbridge.toml";
-        description = ''
+        description = lib.mdDoc ''
           The path to the matterbridge configuration file.
         '';
       };
@@ -62,10 +62,10 @@ in
               account="mattermost.work"
               channel="off-topic"
         '';
-        description = ''
+        description = lib.mdDoc ''
           WARNING: THIS IS INSECURE, as your password will end up in
-          <filename>/nix/store</filename>, thus publicly readable. Use
-          <literal>services.matterbridge.configPath</literal> instead.
+          {file}`/nix/store`, thus publicly readable. Use
+          `services.matterbridge.configPath` instead.
 
           The matterbridge configuration file in the TOML file format.
         '';
@@ -73,7 +73,7 @@ in
       user = mkOption {
         type = types.str;
         default = "matterbridge";
-        description = ''
+        description = lib.mdDoc ''
           User which runs the matterbridge service.
         '';
       };
@@ -81,7 +81,7 @@ in
       group = mkOption {
         type = types.str;
         default = "matterbridge";
-        description = ''
+        description = lib.mdDoc ''
           Group which runs the matterbridge service.
         '';
       };
diff --git a/nixos/modules/services/networking/minidlna.nix b/nixos/modules/services/networking/minidlna.nix
index c860f63efa67..549f1fe5de30 100644
--- a/nixos/modules/services/networking/minidlna.nix
+++ b/nixos/modules/services/networking/minidlna.nix
@@ -1,168 +1,129 @@
 # Module for MiniDLNA, a simple DLNA server.
 { config, lib, pkgs, ... }:
-
 with lib;
 
 let
   cfg = config.services.minidlna;
-  port = 8200;
+  settingsFormat = pkgs.formats.keyValue { listsAsDuplicateKeys = true; };
+  settingsFile = settingsFormat.generate "minidlna.conf" cfg.settings;
 in
 
 {
   ###### interface
-  options = {
-    services.minidlna.enable = mkOption {
-      type = types.bool;
-      default = false;
-      description =
-        ''
-          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.
-        '';
-    };
-
-    services.minidlna.mediaDirs = mkOption {
-      type = types.listOf types.str;
-      default = [];
-      example = [ "/data/media" "V,/home/alice/video" ];
-      description =
-        ''
-          Directories to be scanned for media files.  The prefixes
-          <literal>A,</literal>, <literal>V,</literal> and
-          <literal>P,</literal> restrict a directory to audio, video
-          or image files.  The directories must be accessible to the
-          <literal>minidlna</literal> user account.
-        '';
-    };
-
-    services.minidlna.friendlyName = mkOption {
-      type = types.str;
-      default = "${config.networking.hostName} MiniDLNA";
-      defaultText = literalExpression ''"''${config.networking.hostName} MiniDLNA"'';
-      example = "rpi3";
-      description =
-        ''
-          Name that the DLNA server presents to clients.
-        '';
-    };
-
-    services.minidlna.rootContainer = mkOption {
-      type = types.str;
-      default = ".";
-      example = "B";
-      description =
-        ''
-          Use a different container as the root of the directory tree presented
-          to clients. The possible values are:
-          - "." - standard container
-          - "B" - "Browse Directory"
-          - "M" - "Music"
-          - "P" - "Pictures"
-          - "V" - "Video"
-          - Or, you can specify the ObjectID of your desired root container
-            (eg. 1$F for Music/Playlists)
-          If you specify "B" and the client device is audio-only then
-          "Music/Folders" will be used as root.
-         '';
-    };
-
-    services.minidlna.loglevel = mkOption {
-      type = types.str;
-      default = "warn";
-      example = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn";
-      description =
-        ''
-          Defines the type of messages that should be logged, and down to
-          which level of importance they should be considered.
-
-          The possible types are “artwork”, “database”, “general”, “http”,
-          “inotify”, “metadata”, “scanner”, “ssdp” and “tivo”.
-
-          The levels are “off”, “fatal”, “error”, “warn”, “info” and
-          “debug”, listed here in order of decreasing importance.  “off”
-          turns off logging messages entirely, “fatal” logs the most
-          critical messages only, and so on down to “debug” that logs every
-          single messages.
+  options.services.minidlna.enable = mkOption {
+    type = types.bool;
+    default = false;
+    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
+      adding the following: `services.minidlna.openFirewall = true;`
+    '';
+  };
 
-          The types are comma-separated, followed by an equal sign (‘=’),
-          followed by a level that applies to the preceding types. This can
-          be repeated, separating each of these constructs with a comma.
+  options.services.minidlna.openFirewall = mkOption {
+    type = types.bool;
+    default = false;
+    description = lib.mdDoc ''
+      Whether to open both HTTP (TCP) and SSDP (UDP) ports in the firewall.
+    '';
+  };
 
-          Defaults to “general,artwork,database,inotify,scanner,metadata,
-          http,ssdp,tivo=warn” which logs every type of message at the
-          “warn” level.
+  options.services.minidlna.settings = mkOption {
+    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.
+    '';
+    type = types.submodule {
+      freeformType = settingsFormat.type;
+
+      options.media_dir = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "/data/media" "V,/home/alice/video" ];
+        description = lib.mdDoc ''
+          Directories to be scanned for media files.
+          The `A,` `V,` `P,` prefixes restrict a directory to audio, video or image files.
+          The directories must be accessible to the `minidlna` user account.
         '';
-    };
-
-    services.minidlna.announceInterval = mkOption {
-      type = types.int;
-      default = 895;
-      description =
-        ''
+      };
+      options.notify_interval = mkOption {
+        type = types.int;
+        default = 90000;
+        description = lib.mdDoc ''
           The interval between announces (in seconds).
+          Instead of waiting for announces, you should set `openFirewall` option to use SSDP discovery.
+          Furthermore, this option has been set to 90000 in order to prevent disconnects with certain
+          clients and relies solely on the discovery.
 
-          By default miniDLNA will announce its presence on the network
-          approximately every 15 minutes.
-
-          Many people prefer shorter announce intervals (e.g. 60 seconds)
-          on their home networks, especially when DLNA clients are
-          started on demand.
+          Lower values (e.g. 30 seconds) should be used if you can't use the discovery.
+          Some relevant information can be found here:
+          https://sourceforge.net/p/minidlna/discussion/879957/thread/1389d197/
         '';
-    };
-
-    services.minidlna.config = mkOption {
-      type = types.lines;
-      description =
-      ''
-        The contents of MiniDLNA's configuration file.
-        When the service is activated, a basic template is generated
-        from the current options opened here.
-      '';
-    };
-
-    services.minidlna.extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      example = ''
-        # Not exhaustive example
-        # Support for streaming .jpg and .mp3 files to a TiVo supporting HMO.
-        enable_tivo=no
-        # SSDP notify interval, in seconds.
-        notify_interval=10
-        # maximum number of simultaneous connections
-        # note: many clients open several simultaneous connections while
-        # streaming
-        max_connections=50
-        # set this to yes to allow symlinks that point outside user-defined
-        # media_dirs.
-        wide_links=yes
-      '';
-      description =
-      ''
-        Extra minidlna options not yet opened for configuration here
-        (strict_dlna, model_number, model_name, etc...).  This is appended
-        to the current service already provided.
-      '';
+      };
+      options.port = mkOption {
+        type = types.port;
+        default = 8200;
+        description = lib.mdDoc "Port number for HTTP traffic (descriptions, SOAP, media transfer).";
+      };
+      options.db_dir = mkOption {
+        type = types.path;
+        default = "/var/cache/minidlna";
+        example = "/tmp/minidlna";
+        description = lib.mdDoc "Specify the directory where you want MiniDLNA to store its database and album art cache.";
+      };
+      options.friendly_name = mkOption {
+        type = types.str;
+        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";
+        description = lib.mdDoc "Use a different container as the root of the directory tree presented to clients.";
+      };
+      options.log_level = mkOption {
+        type = types.str;
+        default = "warn";
+        example = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn";
+        description = lib.mdDoc "Defines the type of messages that should be logged and down to which level of importance.";
+      };
+      options.inotify = mkOption {
+        type = types.enum [ "yes" "no" ];
+        default = "no";
+        description = lib.mdDoc "Whether to enable inotify monitoring to automatically discover new files.";
+      };
+      options.enable_tivo = mkOption {
+        type = types.enum [ "yes" "no" ];
+        default = "no";
+        description = lib.mdDoc "Support for streaming .jpg and .mp3 files to a TiVo supporting HMO.";
+      };
+      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_dir`.";
+      };
     };
   };
 
+  imports = [
+    (mkRemovedOptionModule [ "services" "minidlna" "config" ] "")
+    (mkRemovedOptionModule [ "services" "minidlna" "extraConfig" ] "")
+    (mkRenamedOptionModule [ "services" "minidlna" "loglevel"] [ "services" "minidlna" "settings" "log_level" ])
+    (mkRenamedOptionModule [ "services" "minidlna" "rootContainer"] [ "services" "minidlna" "settings" "root_container" ])
+    (mkRenamedOptionModule [ "services" "minidlna" "mediaDirs"] [ "services" "minidlna" "settings" "media_dir" ])
+    (mkRenamedOptionModule [ "services" "minidlna" "friendlyName"] [ "services" "minidlna" "settings" "friendly_name" ])
+    (mkRenamedOptionModule [ "services" "minidlna" "announceInterval"] [ "services" "minidlna" "settings" "notify_interval" ])
+  ];
+
   ###### implementation
   config = mkIf cfg.enable {
-    services.minidlna.config =
-      ''
-        port=${toString port}
-        friendly_name=${cfg.friendlyName}
-        db_dir=/var/cache/minidlna
-        log_level=${cfg.loglevel}
-        inotify=yes
-        root_container=${cfg.rootContainer}
-        ${concatMapStrings (dir: ''
-          media_dir=${dir}
-        '') cfg.mediaDirs}
-        notify_interval=${toString cfg.announceInterval}
-        ${cfg.extraConfig}
-      '';
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.port ];
+    networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 1900 ];
 
     users.users.minidlna = {
       description = "MiniDLNA daemon user";
@@ -186,7 +147,7 @@ in
             PIDFile = "/run/minidlna/pid";
             ExecStart =
               "${pkgs.minidlna}/sbin/minidlnad -S -P /run/minidlna/pid" +
-              " -f ${pkgs.writeText "minidlna.conf" cfg.config}";
+              " -f ${settingsFile}";
           };
       };
   };
diff --git a/nixos/modules/services/networking/miniupnpd.nix b/nixos/modules/services/networking/miniupnpd.nix
index c095d9948546..64aacaf35040 100644
--- a/nixos/modules/services/networking/miniupnpd.nix
+++ b/nixos/modules/services/networking/miniupnpd.nix
@@ -19,11 +19,11 @@ in
 {
   options = {
     services.miniupnpd = {
-      enable = mkEnableOption "MiniUPnP daemon";
+      enable = mkEnableOption (lib.mdDoc "MiniUPnP daemon");
 
       externalInterface = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Name of the external interface.
         '';
       };
@@ -31,17 +31,17 @@ in
       internalIPs = mkOption {
         type = types.listOf types.str;
         example = [ "192.168.1.1/24" "enp1s0" ];
-        description = ''
+        description = lib.mdDoc ''
           The IP address ranges to listen on.
         '';
       };
 
-      natpmp = mkEnableOption "NAT-PMP support";
+      natpmp = mkEnableOption (lib.mdDoc "NAT-PMP support");
 
       upnp = mkOption {
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable UPNP support.
         '';
       };
@@ -49,7 +49,7 @@ in
       appendConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Configuration lines appended to the MiniUPnP config.
         '';
       };
diff --git a/nixos/modules/services/networking/miredo.nix b/nixos/modules/services/networking/miredo.nix
index b7f657efb712..d15a55b4d7d6 100644
--- a/nixos/modules/services/networking/miredo.nix
+++ b/nixos/modules/services/networking/miredo.nix
@@ -20,13 +20,13 @@ in
 
     services.miredo = {
 
-      enable = mkEnableOption "the Miredo IPv6 tunneling service";
+      enable = mkEnableOption (lib.mdDoc "the Miredo IPv6 tunneling service");
 
       package = mkOption {
         type = types.package;
         default = pkgs.miredo;
         defaultText = literalExpression "pkgs.miredo";
-        description = ''
+        description = lib.mdDoc ''
           The package to use for the miredo daemon's binary.
         '';
       };
@@ -34,7 +34,7 @@ in
       serverAddress = mkOption {
         default = "teredo.remlab.net";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The hostname or primary IPv4 address of the Teredo server.
           This setting is required if Miredo runs as a Teredo client.
           "teredo.remlab.net" is an experimental service for testing only.
@@ -45,7 +45,7 @@ in
       interfaceName = mkOption {
         default = "teredo";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Name of the network tunneling interface.
         '';
       };
@@ -53,7 +53,7 @@ in
       bindAddress = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Depending on the local firewall/NAT rules, you might need to force
           Miredo to use a fixed UDP port and or IPv4 address.
         '';
@@ -62,7 +62,7 @@ in
       bindPort = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Depending on the local firewall/NAT rules, you might need to force
           Miredo to use a fixed UDP port and or IPv4 address.
         '';
diff --git a/nixos/modules/services/networking/mjpg-streamer.nix b/nixos/modules/services/networking/mjpg-streamer.nix
index dbc35e2e71c0..8f8d5f5c4d35 100644
--- a/nixos/modules/services/networking/mjpg-streamer.nix
+++ b/nixos/modules/services/networking/mjpg-streamer.nix
@@ -12,12 +12,12 @@ in {
 
     services.mjpg-streamer = {
 
-      enable = mkEnableOption "mjpg-streamer webcam streamer";
+      enable = mkEnableOption (lib.mdDoc "mjpg-streamer webcam streamer");
 
       inputPlugin = mkOption {
         type = types.str;
         default = "input_uvc.so";
-        description = ''
+        description = lib.mdDoc ''
           Input plugin. See plugins documentation for more information.
         '';
       };
@@ -25,8 +25,8 @@ in {
       outputPlugin = mkOption {
         type = types.str;
         default = "output_http.so -w @www@ -n -p 5050";
-        description = ''
-          Output plugin. <literal>@www@</literal> is substituted for default mjpg-streamer www directory.
+        description = lib.mdDoc ''
+          Output plugin. `@www@` is substituted for default mjpg-streamer www directory.
           See plugins documentation for more information.
         '';
       };
@@ -34,13 +34,13 @@ in {
       user = mkOption {
         type = types.str;
         default = "mjpg-streamer";
-        description = "mjpg-streamer user name.";
+        description = lib.mdDoc "mjpg-streamer user name.";
       };
 
       group = mkOption {
         type = types.str;
         default = "video";
-        description = "mjpg-streamer group name.";
+        description = lib.mdDoc "mjpg-streamer group name.";
       };
 
     };
diff --git a/nixos/modules/services/networking/mmsd.nix b/nixos/modules/services/networking/mmsd.nix
new file mode 100644
index 000000000000..7e262a9326c1
--- /dev/null
+++ b/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/nixos/modules/services/networking/monero.nix b/nixos/modules/services/networking/monero.nix
index 8bed89917c85..0de02882acab 100644
--- a/nixos/modules/services/networking/monero.nix
+++ b/nixos/modules/services/networking/monero.nix
@@ -50,12 +50,12 @@ in
 
     services.monero = {
 
-      enable = mkEnableOption "Monero node daemon";
+      enable = mkEnableOption (lib.mdDoc "Monero node daemon");
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/monero";
-        description = ''
+        description = lib.mdDoc ''
           The directory where Monero stores its data files.
         '';
       };
@@ -63,7 +63,7 @@ in
       mining.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to mine monero.
         '';
       };
@@ -71,7 +71,7 @@ in
       mining.address = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Monero address where to send mining rewards.
         '';
       };
@@ -79,16 +79,16 @@ in
       mining.threads = mkOption {
         type = types.addCheck types.int (x: x>=0);
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Number of threads used for mining.
-          Set to <literal>0</literal> to use all available.
+          Set to `0` to use all available.
         '';
       };
 
       rpc.user = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           User name for RPC connections.
         '';
       };
@@ -96,7 +96,7 @@ in
       rpc.password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Password for RPC connections.
         '';
       };
@@ -104,7 +104,7 @@ in
       rpc.address = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           IP address the RPC server will bind to.
         '';
       };
@@ -112,7 +112,7 @@ in
       rpc.port = mkOption {
         type = types.port;
         default = 18081;
-        description = ''
+        description = lib.mdDoc ''
           Port the RPC server will bind to.
         '';
       };
@@ -120,7 +120,7 @@ in
       rpc.restricted = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to restrict RPC to view only commands.
         '';
       };
@@ -128,43 +128,43 @@ in
       limits.upload = mkOption {
         type = types.addCheck types.int (x: x>=-1);
         default = -1;
-        description = ''
+        description = lib.mdDoc ''
           Limit of the upload rate in kB/s.
-          Set to <literal>-1</literal> to leave unlimited.
+          Set to `-1` to leave unlimited.
         '';
       };
 
       limits.download = mkOption {
         type = types.addCheck types.int (x: x>=-1);
         default = -1;
-        description = ''
+        description = lib.mdDoc ''
           Limit of the download rate in kB/s.
-          Set to <literal>-1</literal> to leave unlimited.
+          Set to `-1` to leave unlimited.
         '';
       };
 
       limits.threads = mkOption {
         type = types.addCheck types.int (x: x>=0);
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of threads used for a parallel job.
-          Set to <literal>0</literal> to leave unlimited.
+          Set to `0` to leave unlimited.
         '';
       };
 
       limits.syncSize = mkOption {
         type = types.addCheck types.int (x: x>=0);
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of blocks to sync at once.
-          Set to <literal>0</literal> for adaptive.
+          Set to `0` for adaptive.
         '';
       };
 
       extraNodes = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           List of additional peer IP addresses to add to the local list.
         '';
       };
@@ -172,7 +172,7 @@ in
       priorityNodes = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           List of peer IP addresses to connect to and
           attempt to keep the connection open.
         '';
@@ -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.
         '';
@@ -190,7 +190,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra lines to be added verbatim to monerod configuration.
         '';
       };
diff --git a/nixos/modules/services/networking/morty.nix b/nixos/modules/services/networking/morty.nix
index dff2f482ca6b..72514764a7c6 100644
--- a/nixos/modules/services/networking/morty.nix
+++ b/nixos/modules/services/networking/morty.nix
@@ -17,48 +17,48 @@ 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;
         default = true;
-        description = "Allow IPv6 HTTP requests?";
+        description = lib.mdDoc "Allow IPv6 HTTP requests?";
       };
 
       key = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           HMAC url validation key (hexadecimal encoded).
           Leave blank to disable. Without validation key, anyone can
           submit proxy requests. Leave blank to disable.
-          Generate with <literal>printf %s somevalue | openssl dgst -sha1 -hmac somekey</literal>
+          Generate with `printf %s somevalue | openssl dgst -sha1 -hmac somekey`
         '';
       };
 
       timeout = mkOption {
         type = types.int;
         default = 2;
-        description = "Request timeout in seconds.";
+        description = lib.mdDoc "Request timeout in seconds.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.morty;
         defaultText = literalExpression "pkgs.morty";
-        description = "morty package to use.";
+        description = lib.mdDoc "morty package to use.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
-        description = "Listing port";
+        description = lib.mdDoc "Listing port";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "The address on which the service listens";
+        description = lib.mdDoc "The address on which the service listens";
       };
 
     };
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index b41a2fd27be2..270450cb0c62 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -36,7 +36,7 @@ let
       password = mkOption {
         type = uniq (nullOr str);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the (clear text) password for the MQTT User.
         '';
       };
@@ -45,7 +45,7 @@ let
         type = uniq (nullOr types.path);
         example = "/path/to/file";
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the path to a file containing the
           clear text password for the MQTT user.
         '';
@@ -54,10 +54,12 @@ let
       hashedPassword = mkOption {
         type = uniq (nullOr str);
         default = null;
-        description = ''
+        description = mdDoc ''
           Specifies the hashed password for the MQTT User.
-          To generate hashed password install <literal>mosquitto</literal>
-          package and use <literal>mosquitto_passwd</literal>.
+          To generate hashed password install the `mosquitto`
+          package and use `mosquitto_passwd`, then extract
+          the second field (after the `:`) from the generated
+          file.
         '';
       };
 
@@ -65,11 +67,12 @@ let
         type = uniq (nullOr types.path);
         example = "/path/to/file";
         default = null;
-        description = ''
+        description = mdDoc ''
           Specifies the path to a file containing the
           hashed password for the MQTT user.
-          To generate hashed password install <literal>mosquitto</literal>
-          package and use <literal>mosquitto_passwd</literal>.
+          To generate hashed password install the `mosquitto`
+          package and use `mosquitto_passwd`, then remove the
+          `username:` prefix from the generated file.
         '';
       };
 
@@ -77,7 +80,7 @@ let
         type = listOf str;
         example = [ "read A/B" "readwrite A/#" ];
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Control client access to topics on the broker.
         '';
       };
@@ -155,24 +158,24 @@ let
     options = {
       plugin = mkOption {
         type = path;
-        description = ''
-          Plugin path to load, should be a <literal>.so</literal> file.
+        description = mdDoc ''
+          Plugin path to load, should be a `.so` file.
         '';
       };
 
       denySpecialChars = mkOption {
         type = bool;
-        description = ''
-          Automatically disallow all clients using <literal>#</literal>
-          or <literal>+</literal> in their name/id.
+        description = mdDoc ''
+          Automatically disallow all clients using `#`
+          or `+` in their name/id.
         '';
         default = true;
       };
 
       options = mkOption {
         type = attrsOf optionType;
-        description = ''
-          Options for the auth plugin. Each key turns into a <literal>auth_opt_*</literal>
+        description = mdDoc ''
+          Options for the auth plugin. Each key turns into a `auth_opt_*`
            line in the config.
         '';
         default = {};
@@ -199,6 +202,7 @@ let
     allow_anonymous = 1;
     allow_zero_length_clientid = 1;
     auto_id_prefix = 1;
+    bind_interface = 1;
     cafile = 1;
     capath = 1;
     certfile = 1;
@@ -230,7 +234,7 @@ let
     options = {
       port = mkOption {
         type = port;
-        description = ''
+        description = lib.mdDoc ''
           Port to listen on. Must be set to 0 to listen on a unix domain socket.
         '';
         default = 1883;
@@ -238,8 +242,8 @@ let
 
       address = mkOption {
         type = nullOr str;
-        description = ''
-          Address to listen on. Listen on <literal>0.0.0.0</literal>/<literal>::</literal>
+        description = mdDoc ''
+          Address to listen on. Listen on `0.0.0.0`/`::`
           when unset.
         '';
         default = null;
@@ -247,10 +251,10 @@ let
 
       authPlugins = mkOption {
         type = listOf authPluginOptions;
-        description = ''
+        description = mdDoc ''
           Authentication plugin to attach to this listener.
-          Refer to the <link xlink:href="https://mosquitto.org/man/mosquitto-conf-5.html">
-          mosquitto.conf documentation</link> for details on authentication plugins.
+          Refer to the [mosquitto.conf documentation](https://mosquitto.org/man/mosquitto-conf-5.html)
+          for details on authentication plugins.
         '';
         default = [];
       };
@@ -258,7 +262,7 @@ let
       users = mkOption {
         type = attrsOf userOptions;
         example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; };
-        description = ''
+        description = lib.mdDoc ''
           A set of users and their passwords and ACLs.
         '';
         default = {};
@@ -266,7 +270,7 @@ let
 
       omitPasswordAuth = mkOption {
         type = bool;
-        description = ''
+        description = lib.mdDoc ''
           Omits password checking, allowing anyone to log in with any user name unless
           other mandatory authentication methods (eg TLS client certificates) are configured.
         '';
@@ -275,7 +279,7 @@ let
 
       acl = mkOption {
         type = listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Additional ACL items to prepend to the generated ACL file.
         '';
         example = [ "pattern read #" "topic readwrite anon/report/#" ];
@@ -286,7 +290,7 @@ let
         type = submodule {
           freeformType = attrsOf optionType;
         };
-        description = ''
+        description = lib.mdDoc ''
           Additional settings for this listener.
         '';
         default = {};
@@ -295,7 +299,7 @@ let
   };
 
   listenerAsserts = prefix: listener:
-    assertKeysValid prefix freeformListenerKeys listener.settings
+    assertKeysValid "${prefix}.settings" freeformListenerKeys listener.settings
     ++ userAsserts prefix listener.users
     ++ imap0
       (i: v: authAsserts "${prefix}.authPlugins.${toString i}" v)
@@ -353,14 +357,14 @@ let
           options = {
             address = mkOption {
               type = str;
-              description = ''
+              description = lib.mdDoc ''
                 Address of the remote MQTT broker.
               '';
             };
 
             port = mkOption {
               type = port;
-              description = ''
+              description = lib.mdDoc ''
                 Port of the remote MQTT broker.
               '';
               default = 1883;
@@ -368,17 +372,17 @@ let
           };
         });
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Remote endpoints for the bridge.
         '';
       };
 
       topics = mkOption {
         type = listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Topic patterns to be shared between the two brokers.
-          Refer to the <link xlink:href="https://mosquitto.org/man/mosquitto-conf-5.html">
-          mosquitto.conf documentation</link> for details on the format.
+          Refer to the [
+          mosquitto.conf documentation](https://mosquitto.org/man/mosquitto-conf-5.html) for details on the format.
         '';
         default = [];
         example = [ "# both 2 local/topic/ remote/topic/" ];
@@ -388,7 +392,7 @@ let
         type = submodule {
           freeformType = attrsOf optionType;
         };
-        description = ''
+        description = lib.mdDoc ''
           Additional settings for this bridge.
         '';
         default = {};
@@ -397,7 +401,7 @@ let
   };
 
   bridgeAsserts = prefix: bridge:
-    assertKeysValid prefix freeformBridgeKeys bridge.settings
+    assertKeysValid "${prefix}.settings" freeformBridgeKeys bridge.settings
     ++ [ {
       assertion = length bridge.addresses > 0;
       message = "Bridge ${prefix} needs remote broker addresses";
@@ -442,13 +446,13 @@ let
   };
 
   globalOptions = with types; {
-    enable = mkEnableOption "the MQTT Mosquitto broker";
+    enable = mkEnableOption (lib.mdDoc "the MQTT Mosquitto broker");
 
     package = mkOption {
       type = package;
       default = pkgs.mosquitto;
       defaultText = literalExpression "pkgs.mosquitto";
-      description = ''
+      description = lib.mdDoc ''
         Mosquitto package to use.
       '';
     };
@@ -456,7 +460,7 @@ let
     bridges = mkOption {
       type = attrsOf bridgeOptions;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Bridges to build to other MQTT brokers.
       '';
     };
@@ -464,25 +468,25 @@ let
     listeners = mkOption {
       type = listOf listenerOptions;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Listeners to configure on this broker.
       '';
     };
 
     includeDirs = mkOption {
       type = listOf path;
-      description = ''
+      description = mdDoc ''
         Directories to be scanned for further config files to include.
         Directories will processed in the order given,
-        <literal>*.conf</literal> files in the directory will be
-        read in case-sensistive alphabetical order.
+        `*.conf` files in the directory will be
+        read in case-sensitive alphabetical order.
       '';
       default = [];
     };
 
     logDest = mkOption {
       type = listOf (either path (enum [ "stdout" "stderr" "syslog" "topic" "dlt" ]));
-      description = ''
+      description = lib.mdDoc ''
         Destinations to send log messages to.
       '';
       default = [ "stderr" ];
@@ -491,7 +495,7 @@ let
     logType = mkOption {
       type = listOf (enum [ "debug" "error" "warning" "notice" "information"
                             "subscribe" "unsubscribe" "websockets" "none" "all" ]);
-      description = ''
+      description = lib.mdDoc ''
         Types of messages to log.
       '';
       default = [];
@@ -499,7 +503,7 @@ let
 
     persistence = mkOption {
       type = bool;
-      description = ''
+      description = lib.mdDoc ''
         Enable persistent storage of subscriptions and messages.
       '';
       default = true;
@@ -508,7 +512,7 @@ let
     dataDir = mkOption {
       default = "/var/lib/mosquitto";
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         The data directory.
       '';
     };
@@ -517,7 +521,7 @@ let
       type = submodule {
         freeformType = attrsOf optionType;
       };
-      description = ''
+      description = lib.mdDoc ''
         Global configuration options for the mosquitto broker.
       '';
       default = {};
@@ -526,7 +530,7 @@ let
 
   globalAsserts = prefix: cfg:
     flatten [
-      (assertKeysValid prefix freeformGlobalKeys cfg.settings)
+      (assertKeysValid "${prefix}.settings" freeformGlobalKeys cfg.settings)
       (imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners)
       (mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges)
     ];
@@ -629,9 +633,10 @@ in
                ]));
         RemoveIPC = true;
         RestrictAddressFamilies = [
-          "AF_UNIX"  # for sd_notify() call
+          "AF_UNIX"
           "AF_INET"
           "AF_INET6"
+          "AF_NETLINK"
         ];
         RestrictNamespaces = true;
         RestrictRealtime = true;
diff --git a/nixos/modules/services/networking/mozillavpn.nix b/nixos/modules/services/networking/mozillavpn.nix
index e35ba65314e9..cf962879b421 100644
--- a/nixos/modules/services/networking/mozillavpn.nix
+++ b/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 = ''
-      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/nixos/modules/services/networking/mstpd.nix b/nixos/modules/services/networking/mstpd.nix
index bd71010ce549..ba82c5ac8232 100644
--- a/nixos/modules/services/networking/mstpd.nix
+++ b/nixos/modules/services/networking/mstpd.nix
@@ -9,7 +9,7 @@ with lib;
     enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the multiple spanning tree protocol daemon.
       '';
     };
diff --git a/nixos/modules/services/networking/mtprotoproxy.nix b/nixos/modules/services/networking/mtprotoproxy.nix
index d896f227b82c..3dd197697b23 100644
--- a/nixos/modules/services/networking/mtprotoproxy.nix
+++ b/nixos/modules/services/networking/mtprotoproxy.nix
@@ -37,12 +37,12 @@ in
 
     services.mtprotoproxy = {
 
-      enable = mkEnableOption "mtprotoproxy";
+      enable = mkEnableOption (lib.mdDoc "mtprotoproxy");
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3256;
-        description = ''
+        description = lib.mdDoc ''
           TCP port to accept mtproto connections on.
         '';
       };
@@ -53,7 +53,7 @@ in
           tg = "00000000000000000000000000000000";
           tg2 = "0123456789abcdef0123456789abcdef";
         };
-        description = ''
+        description = lib.mdDoc ''
           Allowed users and their secrets. A secret is a 32 characters long hex string.
         '';
       };
@@ -61,7 +61,7 @@ in
       secureOnly = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Don't allow users to connect in non-secure mode (without random padding).
         '';
       };
@@ -71,7 +71,7 @@ in
         default = null;
         # Taken from mtproxyproto's repo.
         example = "3c09c680b76ee91a4c25ad51f742267d";
-        description = ''
+        description = lib.mdDoc ''
           Tag for advertising that can be obtained from @MTProxybot.
         '';
       };
@@ -82,7 +82,7 @@ in
         example = {
           STATS_PRINT_PERIOD = 600;
         };
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options for mtprotoproxy.
         '';
       };
diff --git a/nixos/modules/services/networking/mtr-exporter.nix b/nixos/modules/services/networking/mtr-exporter.nix
index ca261074ebde..43ebbbe96d05 100644
--- a/nixos/modules/services/networking/mtr-exporter.nix
+++ b/nixos/modules/services/networking/mtr-exporter.nix
@@ -9,37 +9,37 @@ 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;
           example = "example.org";
-          description = "Target to check using MTR.";
+          description = lib.mdDoc "Target to check using MTR.";
         };
 
         interval = mkOption {
           type = types.int;
           default = 60;
-          description = "Interval between MTR checks in seconds.";
+          description = lib.mdDoc "Interval between MTR checks in seconds.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8080;
-          description = "Listen port for MTR exporter.";
+          description = lib.mdDoc "Listen port for MTR exporter.";
         };
 
         address = mkOption {
           type = types.str;
           default = "127.0.0.1";
-          description = "Listen address for MTR exporter.";
+          description = lib.mdDoc "Listen address for MTR exporter.";
         };
 
         mtrFlags = mkOption {
           type = with types; listOf str;
           default = [];
           example = ["-G1"];
-          description = "Additional flags to pass to MTR.";
+          description = lib.mdDoc "Additional flags to pass to MTR.";
         };
       };
     };
diff --git a/nixos/modules/services/networking/mullvad-vpn.nix b/nixos/modules/services/networking/mullvad-vpn.nix
index 9ec1ddc929e1..82e68bf92af1 100644
--- a/nixos/modules/services/networking/mullvad-vpn.nix
+++ b/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 = ''
-      This option enables Mullvad VPN daemon.
-      This sets <option>networking.firewall.checkReversePath</option> 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/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
index 1a44184ff6dc..54ee2a015687 100644
--- a/nixos/modules/services/networking/multipath.nix
+++ b/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 = "multipath-tools package to use";
+      description = lib.mdDoc "multipath-tools package to use";
       default = pkgs.multipath-tools;
-      defaultText = "pkgs.multipath-tools";
+      defaultText = lib.literalExpression "pkgs.multipath-tools";
     };
 
     devices = mkOption {
@@ -44,7 +44,7 @@ in {
           }, ...
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to define arrays for use in multipath
         groups.
       '';
@@ -54,62 +54,62 @@ in {
           vendor = mkOption {
             type = str;
             example = "COMPELNT";
-            description = "Regular expression to match the vendor name";
+            description = lib.mdDoc "Regular expression to match the vendor name";
           };
 
           product = mkOption {
             type = str;
             example = "Compellent Vol";
-            description = "Regular expression to match the product name";
+            description = lib.mdDoc "Regular expression to match the product name";
           };
 
           revision = mkOption {
             type = nullOr str;
             default = null;
-            description = "Regular expression to match the product revision";
+            description = lib.mdDoc "Regular expression to match the product revision";
           };
 
           product_blacklist = mkOption {
             type = nullOr str;
             default = null;
-            description = "Products with the given vendor matching this string are blacklisted";
+            description = lib.mdDoc "Products with the given vendor matching this string are blacklisted";
           };
 
           alias_prefix = mkOption {
             type = nullOr str;
             default = null;
-            description = "The user_friendly_names prefix to use for this device type, instead of the default mpath";
+            description = lib.mdDoc "The user_friendly_names prefix to use for this device type, instead of the default mpath";
           };
 
           vpd_vendor = mkOption {
             type = nullOr str;
             default = null;
-            description = "The vendor specific vpd page information, using the vpd page abbreviation";
+            description = lib.mdDoc "The vendor specific vpd page information, using the vpd page abbreviation";
           };
 
           hardware_handler = mkOption {
             type = nullOr (enum [ "emc" "rdac" "hp_sw" "alua" "ana" ]);
             default = null;
-            description = "The hardware handler to use for this device type";
+            description = lib.mdDoc "The hardware handler to use for this device type";
           };
 
           # Optional arguments
           path_grouping_policy = mkOption {
             type = nullOr (enum [ "failover" "multibus" "group_by_serial" "group_by_prio" "group_by_node_name" ]);
             default = null; # real default: "failover"
-            description = "The default path grouping policy to apply to unspecified multipaths";
+            description = lib.mdDoc "The default path grouping policy to apply to unspecified multipaths";
           };
 
           uid_attribute = mkOption {
             type = nullOr str;
             default = null;
-            description = "The udev attribute providing a unique path identifier (WWID)";
+            description = lib.mdDoc "The udev attribute providing a unique path identifier (WWID)";
           };
 
           getuid_callout = mkOption {
             type = nullOr str;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               (Superseded by uid_attribute) The default program and args to callout
               to obtain a unique path identifier. Should be specified with an absolute path.
             '';
@@ -123,13 +123,13 @@ in {
               ''"historical-service-time 0"''
             ]);
             default = null; # real default: "service-time 0"
-            description = "The default path selector algorithm to use; they are offered by the kernel multipath target";
+            description = lib.mdDoc "The default path selector algorithm to use; they are offered by the kernel multipath target";
           };
 
           path_checker = mkOption {
             type = enum [ "readsector0" "tur" "emc_clariion" "hp_sw" "rdac" "directio" "cciss_tur" "none" ];
             default = "tur";
-            description = "The default method used to determine the paths state";
+            description = lib.mdDoc "The default method used to determine the paths state";
           };
 
           prio = mkOption {
@@ -138,31 +138,31 @@ in {
               "random" "weightedpath" "path_latency" "ana" "datacore" "iet"
             ]);
             default = null; # real default: "const"
-            description = "The name of the path priority routine";
+            description = lib.mdDoc "The name of the path priority routine";
           };
 
           prio_args = mkOption {
             type = nullOr str;
             default = null;
-            description = "Arguments to pass to to the prio function";
+            description = lib.mdDoc "Arguments to pass to to the prio function";
           };
 
           features = mkOption {
             type = nullOr str;
             default = null;
-            description = "Specify any device-mapper features to be used";
+            description = lib.mdDoc "Specify any device-mapper features to be used";
           };
 
           failback = mkOption {
             type = nullOr str;
             default = null; # real default: "manual"
-            description = "Tell multipathd how to manage path group failback. Quote integers as strings";
+            description = lib.mdDoc "Tell multipathd how to manage path group failback. Quote integers as strings";
           };
 
           rr_weight = mkOption {
             type = nullOr (enum [ "priorities" "uniform" ]);
             default = null; # real default: "uniform"
-            description = ''
+            description = lib.mdDoc ''
               If set to priorities the multipath configurator will assign path weights
               as "path prio * rr_min_io".
             '';
@@ -171,13 +171,13 @@ in {
           no_path_retry = mkOption {
             type = nullOr str;
             default = null; # real default: "fail"
-            description = "Specify what to do when all paths are down. Quote integers as strings";
+            description = lib.mdDoc "Specify what to do when all paths are down. Quote integers as strings";
           };
 
           rr_min_io = mkOption {
             type = nullOr int;
             default = null; # real default: 1000
-            description = ''
+            description = lib.mdDoc ''
               Number of I/O requests to route to a path before switching to the next in the
               same path group. This is only for Block I/O (BIO) based multipath and
               only apply to round-robin path_selector.
@@ -187,7 +187,7 @@ in {
           rr_min_io_rq = mkOption {
             type = nullOr int;
             default = null; # real default: 1
-            description = ''
+            description = lib.mdDoc ''
               Number of I/O requests to route to a path before switching to the next in the
               same path group. This is only for Request based multipath and
               only apply to round-robin path_selector.
@@ -197,7 +197,7 @@ in {
           fast_io_fail_tmo = mkOption {
             type = nullOr str;
             default = null; # real default: 5
-            description = ''
+            description = lib.mdDoc ''
               Specify the number of seconds the SCSI layer will wait after a problem has been
               detected on a FC remote port before failing I/O to devices on that remote port.
               This should be smaller than dev_loss_tmo. Setting this to "off" will disable
@@ -208,7 +208,7 @@ in {
           dev_loss_tmo = mkOption {
             type = nullOr str;
             default = null; # real default: 600
-            description = ''
+            description = lib.mdDoc ''
               Specify the number of seconds the SCSI layer will wait after a problem has
               been detected on a FC remote port before removing it from the system. This
               can be set to "infinity" which sets it to the max value of 2147483647
@@ -224,7 +224,7 @@ in {
           flush_on_last_del = mkOption {
             type = nullOr (enum [ "yes" "no" ]);
             default = null; # real default: "no"
-            description = ''
+            description = lib.mdDoc ''
               If set to "yes" multipathd will disable queueing when the last path to a
               device has been deleted.
             '';
@@ -233,7 +233,7 @@ in {
           user_friendly_names = mkOption {
             type = nullOr (enum [ "yes" "no" ]);
             default = null; # real default: "no"
-            description = ''
+            description = lib.mdDoc ''
               If set to "yes", using the bindings file /etc/multipath/bindings
               to assign a persistent and unique alias to the multipath, in the
               form of mpath. If set to "no" use the WWID as the alias. In either
@@ -245,7 +245,7 @@ in {
           detect_prio = mkOption {
             type = nullOr (enum [ "yes" "no" ]);
             default = null; # real default: "yes"
-            description = ''
+            description = lib.mdDoc ''
               If set to "yes", multipath will try to detect if the device supports
               SCSI-3 ALUA. If so, the device will automatically use the sysfs
               prioritizer if the required sysf attributes access_state and
@@ -257,7 +257,7 @@ in {
           detect_checker = mkOption {
             type = nullOr (enum [ "yes" "no" ]);
             default = null; # real default: "yes"
-            description = ''
+            description = lib.mdDoc ''
               If set to "yes", multipath will try to detect if the device supports
               SCSI-3 ALUA. If so, the device will automatically use the tur checker.
               If set to "no", the checker will be selected as usual.
@@ -267,7 +267,7 @@ in {
           deferred_remove = mkOption {
             type = nullOr (enum [ "yes" "no" ]);
             default = null; # real default: "no"
-            description = ''
+            description = lib.mdDoc ''
               If set to "yes", multipathd will do a deferred remove instead of a
               regular remove when the last path device has been deleted. This means
               that if the multipath device is still in use, it will be freed when
@@ -279,7 +279,7 @@ in {
           san_path_err_threshold = mkOption {
             type = nullOr str;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               If set to a value greater than 0, multipathd will watch paths and check
               how many times a path has been failed due to errors.If the number of
               failures on a particular path is greater then the san_path_err_threshold,
@@ -292,7 +292,7 @@ in {
           san_path_err_forget_rate = mkOption {
             type = nullOr str;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               If set to a value greater than 0, multipathd will check whether the path
               failures has exceeded the san_path_err_threshold within this many checks
               i.e san_path_err_forget_rate. If so we will not reinstante the path till
@@ -303,7 +303,7 @@ in {
           san_path_err_recovery_time = mkOption {
             type = nullOr str;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               If set to a value greater than 0, multipathd will make sure that when
               path failures has exceeded the san_path_err_threshold within
               san_path_err_forget_rate then the path will be placed in failed state
@@ -316,61 +316,61 @@ in {
           marginal_path_err_sample_time = mkOption {
             type = nullOr int;
             default = null;
-            description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+            description = lib.mdDoc "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
           };
 
           marginal_path_err_rate_threshold = mkOption {
             type = nullOr int;
             default = null;
-            description = "The error rate threshold as a permillage (1/1000)";
+            description = lib.mdDoc "The error rate threshold as a permillage (1/1000)";
           };
 
           marginal_path_err_recheck_gap_time = mkOption {
             type = nullOr str;
             default = null;
-            description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+            description = lib.mdDoc "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
           };
 
           marginal_path_double_failed_time = mkOption {
             type = nullOr str;
             default = null;
-            description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+            description = lib.mdDoc "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
           };
 
           delay_watch_checks = mkOption {
             type = nullOr str;
             default = null;
-            description = "This option is deprecated, and mapped to san_path_err_forget_rate";
+            description = lib.mdDoc "This option is deprecated, and mapped to san_path_err_forget_rate";
           };
 
           delay_wait_checks = mkOption {
             type = nullOr str;
             default = null;
-            description = "This option is deprecated, and mapped to san_path_err_recovery_time";
+            description = lib.mdDoc "This option is deprecated, and mapped to san_path_err_recovery_time";
           };
 
           skip_kpartx = mkOption {
             type = nullOr (enum [ "yes" "no" ]);
             default = null; # real default: "no"
-            description = "If set to yes, kpartx will not automatically create partitions on the device";
+            description = lib.mdDoc "If set to yes, kpartx will not automatically create partitions on the device";
           };
 
           max_sectors_kb = mkOption {
             type = nullOr int;
             default = null;
-            description = "Sets the max_sectors_kb device parameter on all path devices and the multipath device to the specified value";
+            description = lib.mdDoc "Sets the max_sectors_kb device parameter on all path devices and the multipath device to the specified value";
           };
 
           ghost_delay = mkOption {
             type = nullOr int;
             default = null;
-            description = "Sets the number of seconds that multipath will wait after creating a device with only ghost paths before marking it ready for use in systemd";
+            description = lib.mdDoc "Sets the number of seconds that multipath will wait after creating a device with only ghost paths before marking it ready for use in systemd";
           };
 
           all_tg_pt = mkOption {
             type = nullOr str;
             default = null;
-            description = "Set the 'all targets ports' flag when registering keys with mpathpersist";
+            description = lib.mdDoc "Set the 'all targets ports' flag when registering keys with mpathpersist";
           };
 
         };
@@ -380,7 +380,7 @@ in {
     defaults = mkOption {
       type = nullOr str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         This section defines default values for attributes which are used
         whenever no values are given in the appropriate device or multipath
         sections.
@@ -390,7 +390,7 @@ in {
     blacklist = mkOption {
       type = nullOr str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         This section defines which devices should be excluded from the
         multipath topology discovery.
       '';
@@ -399,7 +399,7 @@ in {
     blacklist_exceptions = mkOption {
       type = nullOr str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         This section defines which devices should be included in the
         multipath topology discovery, despite being listed in the
         blacklist section.
@@ -409,7 +409,7 @@ in {
     overrides = mkOption {
       type = nullOr str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         This section defines values for attributes that should override the
         device-specific settings for all devices.
       '';
@@ -418,13 +418,13 @@ in {
     extraConfig = mkOption {
       type = nullOr str;
       default = null;
-      description = "Lines to append to default multipath.conf";
+      description = lib.mdDoc "Lines to append to default multipath.conf";
     };
 
     extraConfigFile = mkOption {
       type = nullOr str;
       default = null;
-      description = "Append an additional file's contents to /etc/multipath.conf";
+      description = lib.mdDoc "Append an additional file's contents to /etc/multipath.conf";
     };
 
     pathGroups = mkOption {
@@ -439,7 +439,7 @@ in {
           }, ...
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to define multipath groups as described
         in http://christophe.varoqui.free.fr/usage.html.
       '';
@@ -449,34 +449,34 @@ in {
           alias = mkOption {
             type = int;
             example = 1001234;
-            description = "The name of the multipath device";
+            description = lib.mdDoc "The name of the multipath device";
           };
 
           wwid = mkOption {
             type = hexStr;
             example = "360080e500043b35c0123456789abcdef";
-            description = "The identifier for the multipath device";
+            description = lib.mdDoc "The identifier for the multipath device";
           };
 
           array = mkOption {
             type = str;
             default = null;
             example = "bigarray.example.com";
-            description = "The DNS name of the storage array";
+            description = lib.mdDoc "The DNS name of the storage array";
           };
 
           fsType = mkOption {
             type = nullOr str;
             default = null;
             example = "zfs";
-            description = "Type of the filesystem";
+            description = lib.mdDoc "Type of the filesystem";
           };
 
           options = mkOption {
             type = nullOr str;
             default = null;
             example = "ro";
-            description = "Options used to mount the file system";
+            description = lib.mdDoc "Options used to mount the file system";
           };
 
         };
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index 06ec04dbbf16..32498ca25ea8 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -56,23 +56,31 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "If enabled, start the Murmur Mumble server.";
+        description = lib.mdDoc "If enabled, start the Murmur Mumble server.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open ports in the firewall for the Murmur Mumble server.
+        '';
       };
 
       autobanAttempts = mkOption {
         type = types.int;
         default = 10;
-        description = ''
+        description = lib.mdDoc ''
           Number of attempts a client is allowed to make in
-          <literal>autobanTimeframe</literal> seconds, before being
-          banned for <literal>autobanTime</literal>.
+          `autobanTimeframe` seconds, before being
+          banned for `autobanTime`.
         '';
       };
 
       autobanTimeframe = mkOption {
         type = types.int;
         default = 120;
-        description = ''
+        description = lib.mdDoc ''
           Timeframe in which a client can connect without being banned
           for repeated attempts (in seconds).
         '';
@@ -81,51 +89,51 @@ in
       autobanTime = mkOption {
         type = types.int;
         default = 300;
-        description = "The amount of time an IP ban lasts (in seconds).";
+        description = lib.mdDoc "The amount of time an IP ban lasts (in seconds).";
       };
 
       logFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/var/log/murmur/murmurd.log";
-        description = "Path to the log file for Murmur daemon. Empty means log to journald.";
+        description = lib.mdDoc "Path to the log file for Murmur daemon. Empty means log to journald.";
       };
 
       welcometext = mkOption {
         type = types.str;
         default = "";
-        description = "Welcome message for connected clients.";
+        description = lib.mdDoc "Welcome message for connected clients.";
       };
 
       port = mkOption {
         type = types.port;
         default = 64738;
-        description = "Ports to bind to (UDP and TCP).";
+        description = lib.mdDoc "Ports to bind to (UDP and TCP).";
       };
 
       hostName = mkOption {
         type = types.str;
         default = "";
-        description = "Host to bind to. Defaults binding on all addresses.";
+        description = lib.mdDoc "Host to bind to. Defaults binding on all addresses.";
       };
 
       package = mkOption {
         type = types.package;
         default = pkgs.murmur;
         defaultText = literalExpression "pkgs.murmur";
-        description = "Overridable attribute of the murmur package to use.";
+        description = lib.mdDoc "Overridable attribute of the murmur package to use.";
       };
 
       password = mkOption {
         type = types.str;
         default = "";
-        description = "Required password to join server, if specified.";
+        description = lib.mdDoc "Required password to join server, if specified.";
       };
 
       bandwidth = mkOption {
         type = types.int;
         default = 72000;
-        description = ''
+        description = lib.mdDoc ''
           Maximum bandwidth (in bits per second) that clients may send
           speech at.
         '';
@@ -134,25 +142,25 @@ in
       users = mkOption {
         type = types.int;
         default = 100;
-        description = "Maximum number of concurrent clients allowed.";
+        description = lib.mdDoc "Maximum number of concurrent clients allowed.";
       };
 
       textMsgLength = mkOption {
         type = types.int;
         default = 5000;
-        description = "Max length of text messages. Set 0 for no limit.";
+        description = lib.mdDoc "Max length of text messages. Set 0 for no limit.";
       };
 
       imgMsgLength = mkOption {
         type = types.int;
         default = 131072;
-        description = "Max length of image messages. Set 0 for no limit.";
+        description = lib.mdDoc "Max length of image messages. Set 0 for no limit.";
       };
 
       allowHtml = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Allow HTML in client messages, comments, and channel
           descriptions.
         '';
@@ -161,7 +169,7 @@ in
       logDays = mkOption {
         type = types.int;
         default = 31;
-        description = ''
+        description = lib.mdDoc ''
           How long to store RPC logs for in the database. Set 0 to
           keep logs forever, or -1 to disable DB logging.
         '';
@@ -170,7 +178,7 @@ in
       bonjour = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Bonjour auto-discovery, which allows clients over
           your LAN to automatically discover Murmur servers.
         '';
@@ -179,13 +187,13 @@ in
       sendVersion = mkOption {
         type = types.bool;
         default = true;
-        description = "Send Murmur version in UDP response.";
+        description = lib.mdDoc "Send Murmur version in UDP response.";
       };
 
       registerName = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Public server registration name, and also the name of the
           Root channel. Even if you don't publicly register your
           server, you probably still want to set this.
@@ -195,7 +203,7 @@ in
       registerPassword = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Public server registry password, used authenticate your
           server to the registry to prevent impersonation; required for
           subsequent registry updates.
@@ -205,13 +213,13 @@ in
       registerUrl = mkOption {
         type = types.str;
         default = "";
-        description = "URL website for your server.";
+        description = lib.mdDoc "URL website for your server.";
       };
 
       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
@@ -222,58 +230,56 @@ in
       clientCertRequired = mkOption {
         type = types.bool;
         default = false;
-        description = "Require clients to authenticate via certificates.";
+        description = lib.mdDoc "Require clients to authenticate via certificates.";
       };
 
       sslCert = mkOption {
         type = types.str;
         default = "";
-        description = "Path to your SSL certificate.";
+        description = lib.mdDoc "Path to your SSL certificate.";
       };
 
       sslKey = mkOption {
         type = types.str;
         default = "";
-        description = "Path to your SSL key.";
+        description = lib.mdDoc "Path to your SSL key.";
       };
 
       sslCa = mkOption {
         type = types.str;
         default = "";
-        description = "Path to your SSL CA certificate.";
+        description = lib.mdDoc "Path to your SSL CA certificate.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra configuration to put into murmur.ini.";
+        description = lib.mdDoc "Extra configuration to put into murmur.ini.";
       };
 
       environmentFile = mkOption {
         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.
         '';
       };
     };
@@ -291,6 +297,11 @@ in
       gid             = config.ids.gids.murmur;
     };
 
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+      allowedUDPPorts = [ cfg.port ];
+    };
+
     systemd.services.murmur = {
       description = "Murmur Chat Service";
       wantedBy    = [ "multi-user.target" ];
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
index 803f0689d1fd..528a51c1f3af 100644
--- a/nixos/modules/services/networking/mxisd.nix
+++ b/nixos/modules/services/networking/mxisd.nix
@@ -37,32 +37,41 @@ let
 in {
   options = {
     services.mxisd = {
-      enable = mkEnableOption "matrix federated identity server";
+      enable = mkEnableOption (lib.mdDoc "matrix federated identity server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.ma1sd;
         defaultText = literalExpression "pkgs.ma1sd";
-        description = "The mxisd/ma1sd package to use";
+        description = lib.mdDoc "The mxisd/ma1sd package to use";
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = lib.mdDoc ''
+          Path to an environment-file which may contain secrets to be
+          substituted via `envsubst`.
+        '';
       };
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/mxisd";
-        description = "Where data mxisd/ma1sd uses resides";
+        description = lib.mdDoc "Where data mxisd/ma1sd uses resides";
       };
 
       extraConfig = mkOption {
         type = types.attrs;
         default = {};
-        description = "Extra options merged into the mxisd/ma1sd configuration";
+        description = lib.mdDoc "Extra options merged into the mxisd/ma1sd configuration";
       };
 
       matrix = {
 
         domain = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             the domain of the matrix homeserver
           '';
         };
@@ -74,7 +83,7 @@ in {
         name = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Public hostname of mxisd/ma1sd, if different from the Matrix domain.
           '';
         };
@@ -82,7 +91,7 @@ in {
         port = mkOption {
           type = types.nullOr types.int;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             HTTP port to listen on (unencrypted)
           '';
         };
@@ -118,7 +127,13 @@ in {
         Type = "simple";
         User = "mxisd";
         Group = "mxisd";
-        ExecStart = "${cfg.package}/bin/${executable} -c ${configFile}";
+        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+        ExecStart = "${cfg.package}/bin/${executable} -c ${cfg.dataDir}/mxisd-config.yaml";
+        ExecStartPre = "${pkgs.writeShellScript "mxisd-substitute-secrets" ''
+          umask 0077
+          ${pkgs.envsubst}/bin/envsubst -o ${cfg.dataDir}/mxisd-config.yaml \
+            -i ${configFile}
+        ''}";
         WorkingDirectory = cfg.dataDir;
         Restart = "on-failure";
       };
diff --git a/nixos/modules/services/networking/namecoind.nix b/nixos/modules/services/networking/namecoind.nix
index 8f7a5123f7e1..085d6c5fe282 100644
--- a/nixos/modules/services/networking/namecoind.nix
+++ b/nixos/modules/services/networking/namecoind.nix
@@ -44,12 +44,12 @@ in
 
     services.namecoind = {
 
-      enable = mkEnableOption "namecoind, Namecoin client";
+      enable = mkEnableOption (lib.mdDoc "namecoind, Namecoin client");
 
       wallet = mkOption {
         type = types.path;
         default = "${dataDir}/wallet.dat";
-        description = ''
+        description = lib.mdDoc ''
           Wallet file. The ownership of the file has to be
           namecoin:namecoin, and the permissions must be 0640.
         '';
@@ -58,7 +58,7 @@ in
       generate = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to generate (mine) Namecoins.
         '';
       };
@@ -66,7 +66,7 @@ in
       extraNodes = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           List of additional peer IP addresses to connect to.
         '';
       };
@@ -74,7 +74,7 @@ in
       trustedNodes = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           List of the only peer IP addresses to connect to. If specified
           no other connection will be made.
         '';
@@ -83,7 +83,7 @@ in
       rpc.user = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           User name for RPC connections.
         '';
       };
@@ -91,7 +91,7 @@ in
       rpc.password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Password for RPC connections.
         '';
       };
@@ -99,7 +99,7 @@ in
       rpc.address = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           IP address the RPC server will bind to.
         '';
       };
@@ -107,7 +107,7 @@ in
       rpc.port = mkOption {
         type = types.port;
         default = 8332;
-        description = ''
+        description = lib.mdDoc ''
           Port the RPC server will bind to.
         '';
       };
@@ -116,7 +116,7 @@ in
         type = types.nullOr types.path;
         default = null;
         example = "/var/lib/namecoind/server.cert";
-        description = ''
+        description = lib.mdDoc ''
           Certificate file for securing RPC connections.
         '';
       };
@@ -125,7 +125,7 @@ in
         type = types.nullOr types.path;
         default = null;
         example = "/var/lib/namecoind/server.pem";
-        description = ''
+        description = lib.mdDoc ''
           Key file for securing RPC connections.
         '';
       };
@@ -134,7 +134,7 @@ in
       rpc.allowFrom = mkOption {
         type = types.listOf types.str;
         default = [ "127.0.0.1" ];
-        description = ''
+        description = lib.mdDoc ''
           List of IP address ranges allowed to use the RPC API.
           Wiledcards (*) can be user to specify a range.
         '';
diff --git a/nixos/modules/services/networking/nar-serve.nix b/nixos/modules/services/networking/nar-serve.nix
index 745138186a20..beee53c8a242 100644
--- a/nixos/modules/services/networking/nar-serve.nix
+++ b/nixos/modules/services/networking/nar-serve.nix
@@ -10,12 +10,12 @@ 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;
         default = 8383;
-        description = ''
+        description = lib.mdDoc ''
           Port number where nar-serve will listen on.
         '';
       };
@@ -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/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix
index 2e58cd699b25..0b70ae47ccf5 100644
--- a/nixos/modules/services/networking/nat.nix
+++ b/nixos/modules/services/networking/nat.nix
@@ -136,7 +136,7 @@ in
       type = types.bool;
       default = false;
       description =
-        ''
+        lib.mdDoc ''
           Whether to enable Network Address Translation (NAT).
         '';
     };
@@ -145,7 +145,7 @@ in
       type = types.bool;
       default = false;
       description =
-        ''
+        lib.mdDoc ''
           Whether to enable IPv6 NAT.
         '';
     };
@@ -155,7 +155,7 @@ in
       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.
@@ -167,7 +167,7 @@ in
       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.
@@ -179,7 +179,7 @@ in
       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.
@@ -191,7 +191,7 @@ in
       default = null;
       example = "eth1";
       description =
-        ''
+        lib.mdDoc ''
           The name of the external network interface.
         '';
     };
@@ -201,7 +201,7 @@ in
       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
@@ -214,7 +214,7 @@ in
       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
@@ -228,27 +228,27 @@ in
           sourcePort = mkOption {
             type = types.either types.int (types.strMatching "[[:digit:]]+:[[:digit:]]+");
             example = 8080;
-            description = "Source port of the external interface; to specify a port range, use a string with a colon (e.g. \"60000:61000\")";
+            description = lib.mdDoc "Source port of the external interface; to specify a port range, use a string with a colon (e.g. \"60000:61000\")";
           };
 
           destination = mkOption {
             type = types.str;
             example = "10.0.0.1:80";
-            description = "Forward connection to destination ip:port (or [ipv6]:port); to specify a port range, use ip:start-end";
+            description = lib.mdDoc "Forward connection to destination ip:port (or [ipv6]:port); to specify a port range, use ip:start-end";
           };
 
           proto = mkOption {
             type = types.str;
             default = "tcp";
             example = "udp";
-            description = "Protocol of forwarded connection";
+            description = lib.mdDoc "Protocol of forwarded connection";
           };
 
           loopbackIPs = mkOption {
             type = types.listOf types.str;
             default = [];
             example = literalExpression ''[ "55.1.2.3" ]'';
-            description = "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
+            description = lib.mdDoc "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
           };
         };
       });
@@ -258,7 +258,7 @@ in
         { 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.
@@ -270,7 +270,7 @@ in
       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.
         '';
@@ -281,7 +281,7 @@ in
       default = "";
       example = "iptables -A INPUT -p icmp -j ACCEPT";
       description =
-        ''
+        lib.mdDoc ''
           Additional shell commands executed as part of the nat
           initialisation script.
         '';
@@ -292,7 +292,7 @@ in
       default = "";
       example = "iptables -D INPUT -p icmp -j ACCEPT || true";
       description =
-        ''
+        lib.mdDoc ''
           Additional shell commands executed as part of the nat
           teardown script.
         '';
@@ -319,7 +319,10 @@ in
         }
       ];
 
-      environment.systemPackages = [ pkgs.iptables ];
+      # 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" ];
@@ -347,7 +350,7 @@ in
         description = "Network Address Translation";
         wantedBy = [ "network.target" ];
         after = [ "network-pre.target" "systemd-modules-load.service" ];
-        path = [ pkgs.iptables ];
+        path = [ config.networking.firewall.package ];
         unitConfig.ConditionCapability = "CAP_NET_ADMIN";
 
         serviceConfig = {
diff --git a/nixos/modules/services/networking/nats.nix b/nixos/modules/services/networking/nats.nix
index 3e86a4f07bc7..6c21e21b5cb8 100644
--- a/nixos/modules/services/networking/nats.nix
+++ b/nixos/modules/services/networking/nats.nix
@@ -16,35 +16,35 @@ in {
 
   options = {
     services.nats = {
-      enable = mkEnableOption "NATS messaging system";
+      enable = mkEnableOption (lib.mdDoc "NATS messaging system");
 
       user = mkOption {
         type = types.str;
         default = "nats";
-        description = "User account under which NATS runs.";
+        description = lib.mdDoc "User account under which NATS runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "nats";
-        description = "Group under which NATS runs.";
+        description = lib.mdDoc "Group under which NATS runs.";
       };
 
       serverName = mkOption {
         default = "nats";
         example = "n1-c3";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Name of the NATS server, must be unique if clustered.
         '';
       };
 
-      jetstream = mkEnableOption "JetStream";
+      jetstream = mkEnableOption (lib.mdDoc "JetStream");
 
       port = mkOption {
         default = 4222;
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           Port on which to listen.
         '';
       };
@@ -52,7 +52,7 @@ in {
       dataDir = mkOption {
         default = "/var/lib/nats";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The NATS data directory. Only used if JetStream is enabled, for
           storing stream metadata and messages.
 
@@ -74,10 +74,10 @@ in {
             };
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Declarative NATS configuration. See the
-          <link xlink:href="https://docs.nats.io/nats-server/configuration">
-          NATS documentation</link> for a list of options.
+          [
+          NATS documentation](https://docs.nats.io/nats-server/configuration) for a list of options.
         '';
       };
     };
@@ -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/nixos/modules/services/networking/nbd.nix b/nixos/modules/services/networking/nbd.nix
index df3358f51876..454380aa3154 100644
--- a/nixos/modules/services/networking/nbd.nix
+++ b/nixos/modules/services/networking/nbd.nix
@@ -43,12 +43,12 @@ 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;
           default = 10809;
-          description = "Port to listen on. The port is NOT automatically opened in the firewall.";
+          description = lib.mdDoc "Port to listen on. The port is NOT automatically opened in the firewall.";
         };
 
         extraOptions = mkOption {
@@ -56,22 +56,21 @@ in
           default = {
             allowlist = false;
           };
-          description = ''
+          description = lib.mdDoc ''
             Extra options for the server. See
-            <citerefentry><refentrytitle>nbd-server</refentrytitle>
-            <manvolnum>5</manvolnum></citerefentry>.
+            {manpage}`nbd-server(5)`.
           '';
         };
 
         exports = mkOption {
-          description = "Files or block devices to make available over the network.";
+          description = lib.mdDoc "Files or block devices to make available over the network.";
           default = { };
           type = with types; attrsOf
             (submodule {
               options = {
                 path = mkOption {
                   type = str;
-                  description = "File or block device to export.";
+                  description = lib.mdDoc "File or block device to export.";
                   example = "/dev/sdb1";
                 };
 
@@ -79,7 +78,7 @@ in
                   type = nullOr (listOf str);
                   default = null;
                   example = [ "10.10.0.0/24" "127.0.0.1" ];
-                  description = "IPs and subnets that are authorized to connect for this device. If not specified, the server will allow all connections.";
+                  description = lib.mdDoc "IPs and subnets that are authorized to connect for this device. If not specified, the server will allow all connections.";
                 };
 
                 extraOptions = mkOption {
@@ -88,10 +87,9 @@ in
                     flush = true;
                     fua = true;
                   };
-                  description = ''
+                  description = lib.mdDoc ''
                     Extra options for this export. See
-                    <citerefentry><refentrytitle>nbd-server</refentrytitle>
-                    <manvolnum>5</manvolnum></citerefentry>.
+                    {manpage}`nbd-server(5)`.
                   '';
                 };
               };
@@ -100,7 +98,7 @@ in
 
         listenAddress = mkOption {
           type = with types; nullOr str;
-          description = "Address to listen on. If not specified, the server will listen on all interfaces.";
+          description = lib.mdDoc "Address to listen on. If not specified, the server will listen on all interfaces.";
           default = null;
           example = "10.10.0.1";
         };
diff --git a/nixos/modules/services/networking/ncdns.nix b/nixos/modules/services/networking/ncdns.nix
index c8d1b6718e2e..cc97beb14e01 100644
--- a/nixos/modules/services/networking/ncdns.nix
+++ b/nixos/modules/services/networking/ncdns.nix
@@ -50,16 +50,16 @@ 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;
         default = "[::1]";
-        description = ''
+        description = lib.mdDoc ''
           The IP address the ncdns resolver will bind to.  Leave this unchanged
           if you do not wish to directly expose the resolver.
         '';
@@ -68,7 +68,7 @@ in
       port = mkOption {
         type = types.port;
         default = 5333;
-        description = ''
+        description = lib.mdDoc ''
           The port the ncdns resolver will bind to.
         '';
       };
@@ -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.
         '';
       };
@@ -96,7 +96,7 @@ in
         type = types.str;
         default = "";
         example = "root@example.com";
-        description = ''
+        description = lib.mdDoc ''
           An email address for the SOA record at the bit zone.
           If you are only using ncdns locally you can ignore this.
         '';
@@ -105,38 +105,38 @@ in
       identity.address = mkOption {
         type = types.str;
         default = "127.127.127.127";
-        description = ''
+        description = lib.mdDoc ''
           The IP address the hostname specified in
-          <option>services.ncdns.identity.hostname</option> should resolve to.
+          {option}`services.ncdns.identity.hostname` should resolve to.
           If you are only using ncdns locally you can ignore this.
         '';
       };
 
-      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>
+          ```
         '';
       };
 
       dnssec.keys.private = mkOption {
         type = types.path;
         default = defaultFiles.private;
-        description = ''
+        description = lib.mdDoc ''
           Path to the file containing the KSK private key.
         '';
       };
@@ -144,20 +144,20 @@ 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>
+          ```
         '';
       };
 
       dnssec.keys.zonePrivate = mkOption {
         type = types.path;
         default = defaultFiles.zonePrivate;
-        description = ''
+        description = lib.mdDoc ''
           Path to the file containing the ZSK private key.
         '';
       };
@@ -176,11 +176,11 @@ in
             certstore.nssdbdir = "../../home/alice/.pki/nssdb";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           ncdns settings. Use this option to configure ncds
           settings not exposed in a NixOS option or to bypass one.
-          See the example ncdns.conf file at <link xlink:href="
-          https://git.io/JfX7g"/> for the available options.
+          See the example ncdns.conf file at <https://github.com/namecoin/ncdns/blob/master/_doc/ncdns.conf.example>
+          for the available options.
         '';
       };
 
@@ -189,8 +189,8 @@ in
     services.pdns-recursor.resolveNamecoin = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Resolve <literal>.bit</literal> top-level domains using ncdns and namecoin.
+      description = lib.mdDoc ''
+        Resolve `.bit` top-level domains using ncdns and namecoin.
       '';
     };
 
diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix
index 6046ac860cfa..98c58d2d5db1 100644
--- a/nixos/modules/services/networking/ndppd.nix
+++ b/nixos/modules/services/networking/ndppd.nix
@@ -26,7 +26,7 @@ let
     options = {
       interface = mkOption {
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Listen for any Neighbor Solicitation messages on this interface,
           and respond to them according to a set of rules.
           Defaults to the name of the attrset.
@@ -35,22 +35,22 @@ let
       };
       router = mkOption {
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Turns on or off the router flag for Neighbor Advertisement Messages.
         '';
         default = true;
       };
       timeout = mkOption {
         type = types.int;
-        description = ''
-          Controls how long to wait for a Neighbor Advertisment Message before
+        description = lib.mdDoc ''
+          Controls how long to wait for a Neighbor Advertisement Message before
           invalidating the entry, in milliseconds.
         '';
         default = 500;
       };
       ttl = mkOption {
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Controls how long a valid or invalid entry remains in the cache, in
           milliseconds.
         '';
@@ -58,7 +58,7 @@ let
       };
       rules = mkOption {
         type = types.attrsOf rule;
-        description = ''
+        description = lib.mdDoc ''
           This is a rule that the target address is to match against. If no netmask
           is provided, /128 is assumed. You may have several rule sections, and the
           addresses may or may not overlap.
@@ -72,9 +72,9 @@ let
     options = {
       network = mkOption {
         type = types.nullOr types.str;
-        description = ''
+        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.
         '';
@@ -82,7 +82,7 @@ let
       };
       method = mkOption {
         type = types.enum [ "static" "iface" "auto" ];
-        description = ''
+        description = lib.mdDoc ''
           static: Immediately answer any Neighbor Solicitation Messages
             (if they match the IP rule).
           iface: Forward the Neighbor Solicitation Message through the specified
@@ -95,7 +95,7 @@ let
       };
       interface = mkOption {
         type = types.nullOr types.str;
-        description = "Interface to use when method is iface.";
+        description = lib.mdDoc "Interface to use when method is iface.";
         default = null;
       };
     };
@@ -103,33 +103,33 @@ 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";
     };
     configFile = mkOption {
       type = types.nullOr types.path;
-      description = "Path to configuration file.";
+      description = lib.mdDoc "Path to configuration file.";
       default = null;
     };
     routeTTL = mkOption {
       type = types.int;
-      description = ''
+      description = lib.mdDoc ''
         This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route,
         in milliseconds.
       '';
@@ -137,7 +137,7 @@ in {
     };
     proxies = mkOption {
       type = types.attrsOf proxy;
-      description = ''
+      description = lib.mdDoc ''
         This sets up a listener, that will listen for any Neighbor Solicitation
         messages, and respond to them according to a set of rules.
       '';
diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix
index de4439415cf6..2bedafc5d9fe 100644
--- a/nixos/modules/services/networking/nebula.nix
+++ b/nixos/modules/services/networking/nebula.nix
@@ -17,45 +17,45 @@ in
   options = {
     services.nebula = {
       networks = mkOption {
-        description = "Nebula network definitions.";
+        description = lib.mdDoc "Nebula network definitions.";
         default = {};
         type = types.attrsOf (types.submodule {
           options = {
             enable = mkOption {
               type = types.bool;
               default = true;
-              description = "Enable or disable this network.";
+              description = lib.mdDoc "Enable or disable this network.";
             };
 
             package = mkOption {
               type = types.package;
               default = pkgs.nebula;
               defaultText = literalExpression "pkgs.nebula";
-              description = "Nebula derivation to use.";
+              description = lib.mdDoc "Nebula derivation to use.";
             };
 
             ca = mkOption {
               type = types.path;
-              description = "Path to the certificate authority certificate.";
+              description = lib.mdDoc "Path to the certificate authority certificate.";
               example = "/etc/nebula/ca.crt";
             };
 
             cert = mkOption {
               type = types.path;
-              description = "Path to the host certificate.";
+              description = lib.mdDoc "Path to the host certificate.";
               example = "/etc/nebula/host.crt";
             };
 
             key = mkOption {
               type = types.path;
-              description = "Path to the host key.";
+              description = lib.mdDoc "Path to the host key.";
               example = "/etc/nebula/host.key";
             };
 
             staticHostMap = mkOption {
               type = types.attrsOf (types.listOf (types.str));
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).
                 A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.
               '';
@@ -65,13 +65,13 @@ in
             isLighthouse = mkOption {
               type = types.bool;
               default = false;
-              description = "Whether this node is a lighthouse.";
+              description = lib.mdDoc "Whether this node is a lighthouse.";
             };
 
             lighthouses = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = ''
+              description = lib.mdDoc ''
                 List of IPs of lighthouse hosts this node should report to and query from. This should be empty on lighthouse
                 nodes. The IPs should be the lighthouse's Nebula IPs, not their external IPs.
               '';
@@ -81,19 +81,19 @@ in
             listen.host = mkOption {
               type = types.str;
               default = "0.0.0.0";
-              description = "IP address to listen on.";
+              description = lib.mdDoc "IP address to listen on.";
             };
 
             listen.port = mkOption {
               type = types.port;
               default = 4242;
-              description = "Port number to listen on.";
+              description = lib.mdDoc "Port number to listen on.";
             };
 
             tun.disable = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 When tun is disabled, a lighthouse can be started without a local tun interface (and therefore without root).
               '';
             };
@@ -101,29 +101,29 @@ in
             tun.device = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = "Name of the tun device. Defaults to nebula.\${networkName}.";
+              description = lib.mdDoc "Name of the tun device. Defaults to nebula.\${networkName}.";
             };
 
             firewall.outbound = mkOption {
               type = types.listOf types.attrs;
               default = [];
-              description = "Firewall rules for outbound traffic.";
+              description = lib.mdDoc "Firewall rules for outbound traffic.";
               example = [ { port = "any"; proto = "any"; host = "any"; } ];
             };
 
             firewall.inbound = mkOption {
               type = types.listOf types.attrs;
               default = [];
-              description = "Firewall rules for inbound traffic.";
+              description = lib.mdDoc "Firewall rules for inbound traffic.";
               example = [ { port = "any"; proto = "any"; host = "any"; } ];
             };
 
             settings = mkOption {
               type = format.type;
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 Nebula configuration. Refer to
-                <link xlink:href="https://github.com/slackhq/nebula/blob/master/examples/config.yml"/>
+                <https://github.com/slackhq/nebula/blob/master/examples/config.yml>
                 for details on supported values.
               '';
               example = literalExpression ''
@@ -192,6 +192,7 @@ in
                 Group = networkId;
               })
             ];
+            unitConfig.StartLimitIntervalSec = 0; # ensure Restart=always is always honoured (networks can go down for arbitrarily long)
           };
         }) enabledNetworks);
 
diff --git a/nixos/modules/services/networking/netbird.nix b/nixos/modules/services/networking/netbird.nix
new file mode 100644
index 000000000000..5bd9e9ca6169
--- /dev/null
+++ b/nixos/modules/services/networking/netbird.nix
@@ -0,0 +1,64 @@
+{ 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" ];
+      serviceConfig = {
+        AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+        DynamicUser = true;
+        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/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 242afd548df3..3b28cec83cb7 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/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
     '';
   };
 
@@ -157,10 +141,10 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use NetworkManager to obtain an IP address and other
           configuration for all network interfaces that are not manually
-          configured. If enabled, a group <literal>networkmanager</literal>
+          configured. If enabled, a group `networkmanager`
           will be created. Add all users that should have permission
           to change network settings to this group.
         '';
@@ -173,17 +157,14 @@ in {
           str
         ]));
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Configuration for the [connection] section of NetworkManager.conf.
           Refer to
-          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+          [
             https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#id-1.2.3.11
-          </link>
+          ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html)
           or
-          <citerefentry>
-            <refentrytitle>NetworkManager.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>
+          {manpage}`NetworkManager.conf(5)`
           for more information.
         '';
       };
@@ -191,17 +172,14 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Configuration appended to the generated NetworkManager.conf.
           Refer to
-          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+          [
             https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
-          </link>
+          ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html)
           or
-          <citerefentry>
-            <refentrytitle>NetworkManager.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>
+          {manpage}`NetworkManager.conf(5)`
           for more information.
         '';
       };
@@ -209,18 +187,15 @@ in {
       unmanaged = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of interfaces that will not be managed by NetworkManager.
           Interface name can be specified here, but if you need more fidelity,
           refer to
-          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec">
+          [
             https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec
-          </link>
+          ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec)
           or the "Device List Format" Appendix of
-          <citerefentry>
-            <refentrytitle>NetworkManager.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>.
+          {manpage}`NetworkManager.conf(5)`.
         '';
       };
 
@@ -243,7 +218,7 @@ in {
           in
           types.listOf networkManagerPluginPackage;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           List of NetworkManager plug-ins to enable.
           Some plug-ins are enabled by the NetworkManager module by default.
         '';
@@ -252,7 +227,7 @@ in {
       dhcp = mkOption {
         type = types.enum [ "dhcpcd" "internal" ];
         default = "internal";
-        description = ''
+        description = lib.mdDoc ''
           Which program (or internal library) should be used for DHCP.
         '';
       };
@@ -260,7 +235,7 @@ in {
       firewallBackend = mkOption {
         type = types.enum [ "iptables" "nftables" "none" ];
         default = "iptables";
-        description = ''
+        description = lib.mdDoc ''
           Which firewall backend should be used for configuring masquerading with shared mode.
           If set to none, NetworkManager doesn't manage the configuration at all.
         '';
@@ -269,7 +244,7 @@ in {
       logLevel = mkOption {
         type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
         default = "WARN";
-        description = ''
+        description = lib.mdDoc ''
           Set the default logging verbosity level.
         '';
       };
@@ -277,7 +252,7 @@ in {
       appendNameservers = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of name servers that should be appended
           to the ones configured in NetworkManager or received by DHCP.
         '';
@@ -286,7 +261,7 @@ in {
       insertNameservers = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of name servers that should be inserted before
           the ones configured in NetworkManager or received by DHCP.
         '';
@@ -300,16 +275,16 @@ in {
         backend = mkOption {
           type = types.enum [ "wpa_supplicant" "iwd" ];
           default = "wpa_supplicant";
-          description = ''
+          description = lib.mdDoc ''
             Specify the Wi-Fi backend used for the device.
-            Currently supported are <option>wpa_supplicant</option> or <option>iwd</option> (experimental).
+            Currently supported are {option}`wpa_supplicant` or {option}`iwd` (experimental).
           '';
         };
 
         powersave = mkOption {
           type = types.nullOr types.bool;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable Wi-Fi power saving.
           '';
         };
@@ -317,7 +292,7 @@ in {
         scanRandMacAddress = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable MAC address randomization of a Wi-Fi device
             during scanning.
           '';
@@ -327,19 +302,15 @@ in {
       dns = mkOption {
         type = types.enum [ "default" "dnsmasq" "unbound" "systemd-resolved" "none" ];
         default = "default";
-        description = ''
-          Set the DNS (<literal>resolv.conf</literal>) processing mode.
-          </para>
-          <para>
+        description = lib.mdDoc ''
+          Set the DNS (`resolv.conf`) processing mode.
+
           A description of these modes can be found in the main section of
-          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+          [
             https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
-          </link>
+          ](https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html)
           or in
-          <citerefentry>
-            <refentrytitle>NetworkManager.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>.
+          {manpage}`NetworkManager.conf(5)`.
         '';
       };
 
@@ -348,7 +319,7 @@ in {
           options = {
             source = mkOption {
               type = types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path to the hook script.
               '';
             };
@@ -356,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.
               '';
@@ -380,7 +351,7 @@ in {
             ''';
             type = "basic";
         } ]'';
-        description = ''
+        description = lib.mdDoc ''
           A list of scripts which will be executed in response to  network  events.
         '';
       };
@@ -388,23 +359,23 @@ in {
       enableStrongSwan = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the StrongSwan plugin.
-          </para><para>
+
           If you enable this option the
-          <literal>networkmanager_strongswan</literal> plugin will be added to
-          the <option>networking.networkmanager.plugins</option> option
-          so you don't need to to that yourself.
+          `networkmanager_strongswan` plugin will be added to
+          the {option}`networking.networkmanager.plugins` option
+          so you don't need to do that yourself.
         '';
       };
 
       enableFccUnlock = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable 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>
+          [the docs](https://modemmanager.org/docs/modemmanager/fcc-unlock/)
           for more details.
         '';
       };
diff --git a/nixos/modules/services/networking/nextdns.nix b/nixos/modules/services/networking/nextdns.nix
index b070eeec894f..697fa605049e 100644
--- a/nixos/modules/services/networking/nextdns.nix
+++ b/nixos/modules/services/networking/nextdns.nix
@@ -10,13 +10,13 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the NextDNS DNS/53 to DoH Proxy service.";
+        description = lib.mdDoc "Whether to enable the NextDNS DNS/53 to DoH Proxy service.";
       };
       arguments = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "-config" "10.0.3.0/24=abcdef" ];
-        description = "Additional arguments to be passed to nextdns run.";
+        description = lib.mdDoc "Additional arguments to be passed to nextdns run.";
       };
     };
   };
diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix
index b911f97491eb..8166a8e7110b 100644
--- a/nixos/modules/services/networking/nftables.nix
+++ b/nixos/modules/services/networking/nftables.nix
@@ -11,7 +11,7 @@ in
       type = types.bool;
       default = false;
       description =
-        ''
+        lib.mdDoc ''
           Whether to enable nftables.  nftables is a Linux-based packet
           filtering framework intended to replace frameworks like iptables.
 
@@ -21,14 +21,13 @@ in
           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.ruleset = mkOption {
@@ -38,7 +37,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;
 
@@ -77,7 +76,7 @@ in
         }
       '';
       description =
-        ''
+        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.
         '';
@@ -88,9 +87,9 @@ in
         name = "nftables-rules";
         text = cfg.ruleset;
       };
-      defaultText = literalDocBook ''a file with the contents of <option>networking.nftables.ruleset</option>'';
+      defaultText = literalMD ''a file with the contents of {option}`networking.nftables.ruleset`'';
       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.
         '';
diff --git a/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix b/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
index 6523f4b8b9e0..510dc02b5c9f 100644
--- a/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
+++ b/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
@@ -3,7 +3,7 @@
     proto = lib.mkOption {
       type        = lib.types.enum [ "h2" "http/1.1" ];
       default     = "http/1.1";
-      description = ''
+      description = lib.mdDoc ''
         This option configures the protocol the backend server expects
         to use.
 
@@ -15,7 +15,7 @@
     tls = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         This option determines whether nghttpx will negotiate its
         connection with a backend server using TLS or not. The burden
         is on the backend server to provide the TLS certificate!
@@ -28,7 +28,7 @@
     sni = lib.mkOption {
       type        = lib.types.nullOr lib.types.str;
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         Override the TLS SNI field value. This value (in nghttpx)
         defaults to the host value of the backend configuration.
 
@@ -40,7 +40,7 @@
     fall = lib.mkOption {
       type        = lib.types.int;
       default     = 0;
-      description = ''
+      description = lib.mdDoc ''
         If nghttpx cannot connect to the backend N times in a row, the
         backend is assumed to be offline and is excluded from load
         balancing. If N is 0 the backend is never excluded from load
@@ -54,7 +54,7 @@
     rise = lib.mkOption {
       type        = lib.types.int;
       default     = 0;
-      description = ''
+      description = lib.mdDoc ''
         If the backend is excluded from load balancing, nghttpx will
         periodically attempt to make a connection to the backend. If
         the connection is successful N times in a row the backend is
@@ -69,7 +69,7 @@
     affinity = lib.mkOption {
       type        = lib.types.enum [ "ip" "none" ];
       default     = "none";
-      description = ''
+      description = lib.mdDoc ''
         If "ip" is given, client IP based session affinity is
         enabled. If "none" is given, session affinity is disabled.
 
@@ -91,7 +91,7 @@
     dns = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Name resolution of a backends host name is done at start up,
         or configuration reload. If "dns" is true, name resolution
         takes place dynamically.
@@ -108,7 +108,7 @@
     redirect-if-not-tls = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         If true, a backend match requires the frontend connection be
         TLS encrypted. If it is not, nghttpx responds to the request
         with a 308 status code and https URI the client should use
diff --git a/nixos/modules/services/networking/nghttpx/backend-submodule.nix b/nixos/modules/services/networking/nghttpx/backend-submodule.nix
index eb559e926e76..af99b21c9ab3 100644
--- a/nixos/modules/services/networking/nghttpx/backend-submodule.nix
+++ b/nixos/modules/services/networking/nghttpx/backend-submodule.nix
@@ -13,7 +13,7 @@
         host = "127.0.0.1";
         port = 80;
       };
-      description = ''
+      description = lib.mdDoc ''
         Backend server location specified as either a host:port pair
         or a unix domain docket.
       '';
@@ -27,7 +27,7 @@
         "/somepath"
       ];
       default     = [];
-      description = ''
+      description = lib.mdDoc ''
         List of nghttpx backend patterns.
 
         Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
@@ -42,7 +42,7 @@
         tls   = true;
       };
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         Parameters to configure a backend.
       '';
     };
diff --git a/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix b/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
index 33c8572bd14f..66c6d7efa6a0 100644
--- a/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
+++ b/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
@@ -3,7 +3,7 @@
     tls = lib.mkOption {
       type        = lib.types.enum [ "tls" "no-tls" ];
       default     = "tls";
-      description = ''
+      description = lib.mdDoc ''
         Enable or disable TLS. If true (enabled) the key and
         certificate must be configured for nghttpx.
 
@@ -15,7 +15,7 @@
     sni-fwd = lib.mkOption {
       type    = lib.types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         When performing a match to select a backend server, SNI host
         name received from the client is used instead of the request
         host. See --backend option about the pattern match.
@@ -28,7 +28,7 @@
     api = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable API access for this frontend. This enables you to
         dynamically modify nghttpx at run-time therefore this feature
         is disabled by default and should be turned on with care.
@@ -41,7 +41,7 @@
     healthmon = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Make this frontend a health monitor endpoint. Any request
         received on this frontend is responded to with a 200 OK.
 
@@ -53,7 +53,7 @@
     proxyproto = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Accept PROXY protocol version 1 on frontend connection.
 
         Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
diff --git a/nixos/modules/services/networking/nghttpx/frontend-submodule.nix b/nixos/modules/services/networking/nghttpx/frontend-submodule.nix
index 887ef4502131..3175df20eec5 100644
--- a/nixos/modules/services/networking/nghttpx/frontend-submodule.nix
+++ b/nixos/modules/services/networking/nghttpx/frontend-submodule.nix
@@ -13,7 +13,7 @@
         host = "127.0.0.1";
         port = 80;
       };
-      description = ''
+      description = lib.mdDoc ''
         Frontend server interface binding specification as either a
         host:port pair or a unix domain docket.
 
@@ -28,7 +28,7 @@
         tls   = "tls";
       };
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         Parameters to configure a backend.
       '';
     };
diff --git a/nixos/modules/services/networking/nghttpx/nghttpx-options.nix b/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
index 51f1d081b971..82ab8c4223e6 100644
--- a/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
+++ b/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
@@ -1,10 +1,10 @@
 { 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));
-      description = ''
+      description = lib.mdDoc ''
         A list of frontend listener specifications.
       '';
       example = [
@@ -22,7 +22,7 @@
 
     backends  = lib.mkOption {
       type = lib.types.listOf (lib.types.submodule (import ./backend-submodule.nix));
-      description = ''
+      description = lib.mdDoc ''
         A list of backend specifications.
       '';
       example = [
@@ -42,10 +42,10 @@
     tls = lib.mkOption {
       type        = lib.types.nullOr (lib.types.submodule (import ./tls-submodule.nix));
       default     = null;
-      description = ''
+      description = lib.mdDoc ''
         TLS certificate and key paths. Note that this does not enable
         TLS for a frontend listener, to do so, a frontend
-        specification must set <literal>params.tls</literal> to true.
+        specification must set `params.tls` to true.
       '';
       example = {
         key = "/etc/ssl/keys/server.key";
@@ -56,7 +56,7 @@
     extraConfig = lib.mkOption {
       type        = lib.types.lines;
       default     = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra configuration options to be appended to the generated
         configuration file.
       '';
@@ -65,7 +65,7 @@
     single-process = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Run this program in a single process mode for debugging
         purpose. Without this option, nghttpx creates at least 2
         processes: master and worker processes. If this option is
@@ -81,7 +81,7 @@
     backlog = lib.mkOption {
       type        = lib.types.int;
       default     = 65536;
-      description = ''
+      description = lib.mdDoc ''
         Listen backlog size.
 
         Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--backlog
@@ -95,7 +95,7 @@
         "IPv6"
       ];
       default = "auto";
-      description = ''
+      description = lib.mdDoc ''
         Specify address family of backend connections. If "auto" is
         given, both IPv4 and IPv6 are considered. If "IPv4" is given,
         only IPv4 address is considered. If "IPv6" is given, only IPv6
@@ -108,7 +108,7 @@
     workers = lib.mkOption {
       type        = lib.types.int;
       default     = 1;
-      description = ''
+      description = lib.mdDoc ''
         Set the number of worker threads.
 
         Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-n
@@ -118,7 +118,7 @@
     single-thread = lib.mkOption {
       type        = lib.types.bool;
       default     = false;
-      description = ''
+      description = lib.mdDoc ''
         Run everything in one thread inside the worker process. This
         feature is provided for better debugging experience, or for
         the platforms which lack thread support. If threading is
@@ -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/nixos/modules/services/networking/nghttpx/server-options.nix b/nixos/modules/services/networking/nghttpx/server-options.nix
index ef23bfd793c5..48e2a3045596 100644
--- a/nixos/modules/services/networking/nghttpx/server-options.nix
+++ b/nixos/modules/services/networking/nghttpx/server-options.nix
@@ -3,14 +3,14 @@
     host = lib.mkOption {
       type        = lib.types.str;
       example     = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         Server host address.
       '';
     };
     port = lib.mkOption {
       type        = lib.types.int;
       example     = 5088;
-      description = ''
+      description = lib.mdDoc ''
         Server host port.
       '';
     };
diff --git a/nixos/modules/services/networking/nghttpx/tls-submodule.nix b/nixos/modules/services/networking/nghttpx/tls-submodule.nix
index 8f3cdaae2c81..bb6cdae07e58 100644
--- a/nixos/modules/services/networking/nghttpx/tls-submodule.nix
+++ b/nixos/modules/services/networking/nghttpx/tls-submodule.nix
@@ -4,7 +4,7 @@
       type        = lib.types.str;
       example     = "/etc/ssl/keys/mykeyfile.key";
       default     = "/etc/ssl/keys/server.key";
-      description = ''
+      description = lib.mdDoc ''
         Path to the TLS key file.
       '';
     };
@@ -13,7 +13,7 @@
       type        = lib.types.str;
       example     = "/etc/ssl/certs/mycert.crt";
       default     = "/etc/ssl/certs/server.crt";
-      description = ''
+      description = lib.mdDoc ''
         Path to the TLS certificate file.
       '';
     };
diff --git a/nixos/modules/services/networking/ngircd.nix b/nixos/modules/services/networking/ngircd.nix
index c0b9c98fb4bf..5e721f5aa625 100644
--- a/nixos/modules/services/networking/ngircd.nix
+++ b/nixos/modules/services/networking/ngircd.nix
@@ -20,16 +20,16 @@ let
 in {
   options = {
     services.ngircd = {
-      enable = mkEnableOption "the ngircd IRC server";
+      enable = mkEnableOption (lib.mdDoc "the ngircd IRC server");
 
       config = mkOption {
-        description = "The ngircd configuration (see ngircd.conf(5)).";
+        description = lib.mdDoc "The ngircd configuration (see ngircd.conf(5)).";
 
         type = types.lines;
       };
 
       package = mkOption {
-        description = "The ngircd package.";
+        description = lib.mdDoc "The ngircd package.";
 
         type = types.package;
 
diff --git a/nixos/modules/services/networking/nix-serve.nix b/nixos/modules/services/networking/nix-serve.nix
index 432938d59d90..f37be31270b7 100644
--- a/nixos/modules/services/networking/nix-serve.nix
+++ b/nixos/modules/services/networking/nix-serve.nix
@@ -8,12 +8,12 @@ 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;
         default = 5000;
-        description = ''
+        description = lib.mdDoc ''
           Port number where nix-serve will listen on.
         '';
       };
@@ -21,21 +21,30 @@ in
       bindAddress = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           IP address where nix-serve will bind its listening socket.
         '';
       };
 
+      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;
-        description = "Open ports in the firewall for nix-serve.";
+        description = lib.mdDoc "Open ports in the firewall for nix-serve.";
       };
 
       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,14 +52,14 @@ 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)`.
         '';
       };
 
       extraParams = mkOption {
         type = types.separatedString " ";
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra command line parameters for nix-serve.
         '';
       };
@@ -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/nixos/modules/services/networking/nix-store-gcs-proxy.nix b/nixos/modules/services/networking/nix-store-gcs-proxy.nix
index 0012302db2e3..531b2bde7633 100644
--- a/nixos/modules/services/networking/nix-store-gcs-proxy.nix
+++ b/nixos/modules/services/networking/nix-store-gcs-proxy.nix
@@ -9,18 +9,18 @@ let
         default = true;
         type = types.bool;
         example = true;
-        description = "Whether to enable proxy for this bucket";
+        description = lib.mdDoc "Whether to enable proxy for this bucket";
       };
       bucketName = mkOption {
         type = types.str;
         default = name;
         example = "my-bucket-name";
-        description = "Name of Google storage bucket";
+        description = lib.mdDoc "Name of Google storage bucket";
       };
       address = mkOption {
         type = types.str;
         example = "localhost:3000";
-        description = "The address of the proxy.";
+        description = lib.mdDoc "The address of the proxy.";
       };
     };
   };
@@ -31,7 +31,7 @@ in
   options.services.nix-store-gcs-proxy = mkOption {
     type = types.attrsOf (types.submodule opts);
     default = {};
-    description = ''
+    description = lib.mdDoc ''
       An attribute set describing an HTTP to GCS proxy that allows us to use GCS
       bucket via HTTP protocol.
     '';
diff --git a/nixos/modules/services/networking/nixops-dns.nix b/nixos/modules/services/networking/nixops-dns.nix
index 5e33d872ea45..378c2ee6d05f 100644
--- a/nixos/modules/services/networking/nixops-dns.nix
+++ b/nixos/modules/services/networking/nixops-dns.nix
@@ -12,7 +12,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the nixops-dns resolution
           of NixOps virtual machines via dnsmasq and fake domain name.
         '';
@@ -20,7 +20,7 @@ in
 
       user = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The user the nixops-dns daemon should run as.
           This should be the user, which is also used for nixops and
           have the .nixops directory in its home.
@@ -29,7 +29,7 @@ in
 
       domain = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Fake domain name to resolve to NixOps virtual machines.
 
           For example "ops" will resolve "vm.ops".
@@ -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/nixos/modules/services/networking/nntp-proxy.nix b/nixos/modules/services/networking/nntp-proxy.nix
index a5973cd59334..b887c0e16ef4 100644
--- a/nixos/modules/services/networking/nntp-proxy.nix
+++ b/nixos/modules/services/networking/nntp-proxy.nix
@@ -59,21 +59,21 @@ in
   options = {
 
     services.nntp-proxy = {
-      enable = mkEnableOption "NNTP-Proxy";
+      enable = mkEnableOption (lib.mdDoc "NNTP-Proxy");
 
       upstreamServer = mkOption {
         type = types.str;
         default = "";
         example = "ssl-eu.astraweb.com";
-        description = ''
+        description = lib.mdDoc ''
           Upstream server address
         '';
       };
 
       upstreamPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 563;
-        description = ''
+        description = lib.mdDoc ''
           Upstream server port
         '';
       };
@@ -81,7 +81,7 @@ in
       upstreamMaxConnections = mkOption {
         type = types.int;
         default = 20;
-        description = ''
+        description = lib.mdDoc ''
           Upstream server maximum allowed concurrent connections
         '';
       };
@@ -89,7 +89,7 @@ in
       upstreamUser = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Upstream server username
         '';
       };
@@ -97,7 +97,7 @@ in
       upstreamPassword = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Upstream server password
         '';
       };
@@ -106,15 +106,15 @@ in
         type = types.str;
         default = "127.0.0.1";
         example = "[::]";
-        description = ''
+        description = lib.mdDoc ''
           Proxy listen address (IPv6 literal addresses need to be enclosed in "[" and "]" characters)
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 5555;
-        description = ''
+        description = lib.mdDoc ''
           Proxy listen port
         '';
       };
@@ -123,7 +123,7 @@ in
         type = types.str;
         default = "key.pem";
         example = "/path/to/your/key.file";
-        description = ''
+        description = lib.mdDoc ''
           Proxy ssl key path
         '';
       };
@@ -132,7 +132,7 @@ in
         type = types.str;
         default = "cert.pem";
         example = "/path/to/your/cert.file";
-        description = ''
+        description = lib.mdDoc ''
           Proxy ssl certificate path
         '';
       };
@@ -140,7 +140,7 @@ in
       prohibitPosting = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to prohibit posting to the upstream server
         '';
       };
@@ -149,7 +149,7 @@ in
         type = types.enum [ "error" "warning" "notice" "info" "debug" ];
         default = "info";
         example = "error";
-        description = ''
+        description = lib.mdDoc ''
           Verbosity level
         '';
       };
@@ -159,7 +159,7 @@ in
           options = {
             username = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Username
               '';
             };
@@ -167,22 +167,22 @@ in
             passwordHash = mkOption {
               type = types.str;
               example = "$6$GtzE7FrpE$wwuVgFYU.TZH4Rz.Snjxk9XGua89IeVwPQ/fEUD8eujr40q5Y021yhn0aNcsQ2Ifw.BLclyzvzgegopgKcneL0";
-              description = ''
+              description = lib.mdDoc ''
                 SHA-512 password hash (can be generated by
-                <code>mkpasswd -m sha-512 &lt;password&gt;</code>)
+                `mkpasswd -m sha-512 <password>`)
               '';
             };
 
             maxConnections = mkOption {
               type = types.int;
               default = 1;
-              description = ''
+              description = lib.mdDoc ''
                 Maximum number of concurrent connections to the proxy for this user
               '';
             };
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           NNTP-Proxy user configuration
         '';
 
diff --git a/nixos/modules/services/networking/nomad.nix b/nixos/modules/services/networking/nomad.nix
index 43333af5e2fe..890ee0b7d8d1 100644
--- a/nixos/modules/services/networking/nomad.nix
+++ b/nixos/modules/services/networking/nomad.nix
@@ -8,13 +8,13 @@ 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;
         default = pkgs.nomad;
         defaultText = literalExpression "pkgs.nomad";
-        description = ''
+        description = lib.mdDoc ''
           The package used for the Nomad agent and CLI.
         '';
       };
@@ -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 ]
@@ -33,7 +33,7 @@ in
       dropPrivileges = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether the nomad agent should be run as a non-root nomad user.
         '';
       };
@@ -41,7 +41,7 @@ in
       enableDocker = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable Docker support. Needed for Nomad's docker driver.
 
           Note that the docker group membership is effectively equivalent
@@ -52,7 +52,7 @@ in
       extraSettingsPaths = mkOption {
         type = types.listOf types.path;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           Additional settings paths used to configure nomad. These can be files or directories.
         '';
         example = literalExpression ''
@@ -63,11 +63,11 @@ in
       extraSettingsPlugins = mkOption {
         type = types.listOf (types.either types.package types.path);
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           Additional plugins dir used to configure nomad.
         '';
         example = literalExpression ''
-          [ "<pluginDir>" "pkgs.<plugins-name>"]
+          [ "<pluginDir>" pkgs.<plugins-name> ]
         '';
       };
 
@@ -75,23 +75,23 @@ in
       settings = mkOption {
         type = format.type;
         default = { };
-        description = ''
-          Configuration for Nomad. See the <link xlink:href="https://www.nomadproject.io/docs/configuration">documentation</link>
+        description = lib.mdDoc ''
+          Configuration for Nomad. See the [documentation](https://www.nomadproject.io/docs/configuration)
           for supported values.
 
-          Notes about <literal>data_dir</literal>:
+          Notes about `data_dir`:
 
-          If <literal>data_dir</literal> is set to a value other than the
-          default value of <literal>"/var/lib/nomad"</literal> it is the Nomad
+          If `data_dir` is set to a value other than the
+          default value of `"/var/lib/nomad"` it is the Nomad
           cluster manager's responsibility to make sure that this directory
           exists and has the appropriate permissions.
 
-          Additionally, if <literal>dropPrivileges</literal> is
-          <literal>true</literal> then <literal>data_dir</literal>
-          <emphasis>cannot</emphasis> be customized. Setting
-          <literal>dropPrivileges</literal> to <literal>true</literal> enables
-          the <literal>DynamicUser</literal> feature of systemd which directly
-          manages and operates on <literal>StateDirectory</literal>.
+          Additionally, if `dropPrivileges` is
+          `true` then `data_dir`
+          *cannot* be customized. Setting
+          `dropPrivileges` to `true` enables
+          the `DynamicUser` feature of systemd which directly
+          manages and operates on `StateDirectory`.
         '';
         example = literalExpression ''
           {
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix
index a51fc5345342..09f3bdc7ae07 100644
--- a/nixos/modules/services/networking/nsd.nix
+++ b/nixos/modules/services/networking/nsd.nix
@@ -201,7 +201,7 @@ let
       allowAXFRFallback = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           If NSD as secondary server should be allowed to AXFR if the primary
           server does not allow IXFR.
         '';
@@ -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
         '';
       };
 
@@ -243,7 +243,7 @@ let
         # to default values, breaking the parent inheriting function.
         type = types.attrsOf types.anything;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Children zones inherit all options of their parents. Attributes
           defined in a child will overwrite the ones of its parent. Only
           leaf zones will be actually served. This way it's possible to
@@ -256,29 +256,29 @@ let
       data = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           The actual zone data. This is the content of your zone file.
           Use imports or pkgs.lib.readFile if you don't want this data in your config file.
         '';
       };
 
-      dnssec = mkEnableOption "DNSSEC";
+      dnssec = mkEnableOption (lib.mdDoc "DNSSEC");
 
       dnssecPolicy = {
         algorithm = mkOption {
           type = types.str;
           default = "RSASHA256";
-          description = "Which algorithm to use for DNSSEC";
+          description = lib.mdDoc "Which algorithm to use for DNSSEC";
         };
         keyttl = mkOption {
           type = types.str;
           default = "1h";
-          description = "TTL for dnssec records";
+          description = lib.mdDoc "TTL for dnssec records";
         };
         coverage = mkOption {
           type = types.str;
           default = "1y";
-          description = ''
+          description = lib.mdDoc ''
             The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time.
           '';
         };
@@ -289,7 +289,7 @@ let
                       postPublish = "1w";
                       rollPeriod = "1mo";
                     };
-          description = "Key policy for zone signing keys";
+          description = lib.mdDoc "Key policy for zone signing keys";
         };
         ksk = mkOption {
           type = keyPolicy;
@@ -298,14 +298,14 @@ let
                       postPublish = "1mo";
                       rollPeriod = "0";
                     };
-          description = "Key policy for key signing keys";
+          description = lib.mdDoc "Key policy for key signing keys";
         };
       };
 
       maxRefreshSecs = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Limit refresh time for secondary zones. This is the timer which
           checks to see if the zone has to be refetched when it expires.
           Normally the value from the SOA record is used, but this  option
@@ -316,7 +316,7 @@ let
       minRefreshSecs = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Limit refresh time for secondary zones.
         '';
       };
@@ -324,7 +324,7 @@ let
       maxRetrySecs = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Limit retry time for secondary zones. This is the timeout after
           a failed fetch attempt for the zone. Normally the value from
           the SOA record is used, but this option restricts that value.
@@ -334,7 +334,7 @@ let
       minRetrySecs = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Limit retry time for secondary zones.
         '';
       };
@@ -344,25 +344,24 @@ 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
         '';
       };
 
       notifyRetry = mkOption {
         type = types.int;
         default = 5;
-        description = ''
+        description = lib.mdDoc ''
           Specifies the number of retries for failed notifies. Set this along with notify.
         '';
       };
@@ -371,8 +370,8 @@ let
         type = types.nullOr types.str;
         default = null;
         example = "2000::1@1234";
-        description = ''
-          This address will be used for zone-transfere requests if configured
+        description = lib.mdDoc ''
+          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,24 +382,24 @@ 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
         '';
       };
 
       requestXFR = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
-          Format: <code>[AXFR|UDP] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</code>
+        description = lib.mdDoc ''
+          Format: `[AXFR|UDP] <ip-address> <key-name | NOKEY>`
         '';
       };
 
       rrlWhitelist = mkOption {
         type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]);
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Whitelists the given rrl-types.
         '';
       };
@@ -409,7 +408,7 @@ let
         type = types.nullOr types.str;
         default = null;
         example = "%s";
-        description = ''
+        description = lib.mdDoc ''
           When set to something distinct to null NSD is able to collect
           statistics per zone. All statistics of this zone(s) will be added
           to the group specified by this given name. Use "%s" to use the zones
@@ -424,19 +423,19 @@ let
     options = {
       keySize = mkOption {
         type = types.int;
-        description = "Key size in bits";
+        description = lib.mdDoc "Key size in bits";
       };
       prePublish = mkOption {
         type = types.str;
-        description = "How long in advance to publish new keys";
+        description = lib.mdDoc "How long in advance to publish new keys";
       };
       postPublish = mkOption {
         type = types.str;
-        description = "How long after deactivation to keep a key in the zone";
+        description = lib.mdDoc "How long after deactivation to keep a key in the zone";
       };
       rollPeriod = mkOption {
         type = types.str;
-        description = "How frequently to change keys";
+        description = lib.mdDoc "How frequently to change keys";
       };
     };
   };
@@ -479,14 +478,14 @@ 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;
       default = "1h";
-      description = ''
+      description = lib.mdDoc ''
         How often to check whether dnssec key rollover is required
       '';
     };
@@ -494,7 +493,7 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra nsd config.
       '';
     };
@@ -502,7 +501,7 @@ in
     hideVersion = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries.
       '';
     };
@@ -510,7 +509,7 @@ in
     identity = mkOption {
       type = types.str;
       default = "unidentified server";
-      description = ''
+      description = lib.mdDoc ''
         Identify the server (CH TXT ID.SERVER entry).
       '';
     };
@@ -518,7 +517,7 @@ in
     interfaces = mkOption {
       type = types.listOf types.str;
       default = [ "127.0.0.0" "::1" ];
-      description = ''
+      description = lib.mdDoc ''
         What addresses the server should listen to.
       '';
     };
@@ -526,7 +525,7 @@ in
     ipFreebind = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to bind to nonlocal addresses and interfaces that are down.
         Similar to ip-transparent.
       '';
@@ -535,7 +534,7 @@ in
     ipTransparent = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Allow binding to non local addresses.
       '';
     };
@@ -543,7 +542,7 @@ in
     ipv4 = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to listen on IPv4 connections.
       '';
     };
@@ -551,7 +550,7 @@ in
     ipv4EDNSSize = mkOption {
       type = types.int;
       default = 4096;
-      description = ''
+      description = lib.mdDoc ''
         Preferred EDNS buffer size for IPv4.
       '';
     };
@@ -559,7 +558,7 @@ in
     ipv6 = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to listen on IPv6 connections.
       '';
     };
@@ -567,7 +566,7 @@ in
     ipv6EDNSSize = mkOption {
       type = types.int;
       default = 4096;
-      description = ''
+      description = lib.mdDoc ''
         Preferred EDNS buffer size for IPv6.
       '';
     };
@@ -575,7 +574,7 @@ in
     logTimeAscii = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Log time in ascii, if false then in unix epoch seconds.
       '';
     };
@@ -583,15 +582,15 @@ in
     nsid = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         NSID identity (hex string, or "ascii_somestring").
       '';
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 53;
-      description = ''
+      description = lib.mdDoc ''
         Port the service should bind do.
       '';
     };
@@ -600,7 +599,7 @@ in
       type = types.bool;
       default = pkgs.stdenv.isLinux;
       defaultText = literalExpression "pkgs.stdenv.isLinux";
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
         processes bind to the same port. This speeds up operation especially
         if the server count is greater than one and makes fast restarts less
@@ -611,18 +610,18 @@ in
     rootServer = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether this server will be a root server (a DNS root server, you
         usually don't want that).
       '';
     };
 
-    roundRobin = mkEnableOption "round robin rotation of records";
+    roundRobin = mkEnableOption (lib.mdDoc "round robin rotation of records");
 
     serverCount = mkOption {
       type = types.int;
       default = 1;
-      description = ''
+      description = lib.mdDoc ''
         Number of NSD servers to fork. Put the number of CPUs to use here.
       '';
     };
@@ -630,7 +629,7 @@ in
     statistics = mkOption {
       type = types.nullOr types.int;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Statistics are produced every number of seconds. Prints to log.
         If null no statistics are logged.
       '';
@@ -639,7 +638,7 @@ in
     tcpCount = mkOption {
       type = types.int;
       default = 100;
-      description = ''
+      description = lib.mdDoc ''
         Maximum number of concurrent TCP connections per server.
       '';
     };
@@ -647,7 +646,7 @@ in
     tcpQueryCount = mkOption {
       type = types.int;
       default = 0;
-      description = ''
+      description = lib.mdDoc ''
         Maximum number of queries served on a single TCP connection.
         0 means no maximum.
       '';
@@ -656,7 +655,7 @@ in
     tcpTimeout = mkOption {
       type = types.int;
       default = 120;
-      description = ''
+      description = lib.mdDoc ''
         TCP timeout in seconds.
       '';
     };
@@ -664,7 +663,7 @@ in
     verbosity = mkOption {
       type = types.int;
       default = 0;
-      description = ''
+      description = lib.mdDoc ''
         Verbosity level.
       '';
     };
@@ -672,7 +671,7 @@ in
     version = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The version string replied for CH TXT version.server and version.bind
         queries. Will use the compiled package version on null.
         See hideVersion for enabling/disabling this responses.
@@ -682,7 +681,7 @@ in
     xfrdReloadTimeout = mkOption {
       type = types.int;
       default = 1;
-      description = ''
+      description = lib.mdDoc ''
         Number of seconds between reloads triggered by xfrd.
       '';
     };
@@ -690,7 +689,7 @@ in
     zonefilesCheck = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to check mtime of all zone files on start and sighup.
       '';
     };
@@ -703,14 +702,14 @@ in
           algorithm = mkOption {
             type = types.str;
             default = "hmac-sha256";
-            description = ''
+            description = lib.mdDoc ''
               Authentication algorithm for this key.
             '';
           };
 
           keyFile = mkOption {
             type = types.path;
-            description = ''
+            description = lib.mdDoc ''
               Path to the file which contains the actual base64 encoded
               key. The key will be copied into "${stateDir}/private" before
               NSD starts. The copied file is only accessibly by the NSD
@@ -728,7 +727,7 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Define your TSIG keys here.
       '';
     };
@@ -736,12 +735,12 @@ in
 
     ratelimit = {
 
-      enable = mkEnableOption "ratelimit capabilities";
+      enable = mkEnableOption (lib.mdDoc "ratelimit capabilities");
 
       ipv4PrefixLength = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           IPv4 prefix length. Addresses are grouped by netblock.
         '';
       };
@@ -749,7 +748,7 @@ in
       ipv6PrefixLength = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           IPv6 prefix length. Addresses are grouped by netblock.
         '';
       };
@@ -757,7 +756,7 @@ in
       ratelimit = mkOption {
         type = types.int;
         default = 200;
-        description = ''
+        description = lib.mdDoc ''
           Max qps allowed from any query source.
           0 means unlimited. With an verbosity of 2 blocked and
           unblocked subnets will be logged.
@@ -767,7 +766,7 @@ in
       slip = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Number of packets that get discarded before replying a SLIP response.
           0 disables SLIP responses. 1 will make every response a SLIP response.
         '';
@@ -776,7 +775,7 @@ in
       size = mkOption {
         type = types.int;
         default = 1000000;
-        description = ''
+        description = lib.mdDoc ''
           Size of the hashtable. More buckets use more memory but lower
           the chance of hash hash collisions.
         '';
@@ -785,7 +784,7 @@ in
       whitelistRatelimit = mkOption {
         type = types.int;
         default = 2000;
-        description = ''
+        description = lib.mdDoc ''
           Max qps allowed from whitelisted sources.
           0 means unlimited. Set the rrl-whitelist option for specific
           queries to apply this limit instead of the default to them.
@@ -797,12 +796,12 @@ in
 
     remoteControl = {
 
-      enable = mkEnableOption "remote control via nsd-control";
+      enable = mkEnableOption (lib.mdDoc "remote control via nsd-control");
 
       controlCertFile = mkOption {
         type = types.path;
         default = "/etc/nsd/nsd_control.pem";
-        description = ''
+        description = lib.mdDoc ''
           Path to the client certificate signed with the server certificate.
           This file is used by nsd-control and generated by nsd-control-setup.
         '';
@@ -811,7 +810,7 @@ in
       controlKeyFile = mkOption {
         type = types.path;
         default = "/etc/nsd/nsd_control.key";
-        description = ''
+        description = lib.mdDoc ''
           Path to the client private key, which is used by nsd-control
           but not by the server. This file is generated by nsd-control-setup.
         '';
@@ -820,15 +819,15 @@ in
       interfaces = mkOption {
         type = types.listOf types.str;
         default = [ "127.0.0.1" "::1" ];
-        description = ''
+        description = lib.mdDoc ''
           Which interfaces NSD should bind to for remote control.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8952;
-        description = ''
+        description = lib.mdDoc ''
           Port number for remote control operations (uses TLS over TCP).
         '';
       };
@@ -836,7 +835,7 @@ in
       serverCertFile = mkOption {
         type = types.path;
         default = "/etc/nsd/nsd_server.pem";
-        description = ''
+        description = lib.mdDoc ''
           Path to the server self signed certificate, which is used by the server
           but and by nsd-control. This file is generated by nsd-control-setup.
         '';
@@ -845,7 +844,7 @@ in
       serverKeyFile = mkOption {
         type = types.path;
         default = "/etc/nsd/nsd_server.key";
-        description = ''
+        description = lib.mdDoc ''
           Path to the server private key, which is used by the server
           but not by nsd-control. This file is generated by nsd-control-setup.
         '';
@@ -887,7 +886,7 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Define your zones here. Zones can cascade other zones and therefore
         inherit settings from parent zones. Look at the definition of
         children to learn about inheritance and child zones.
diff --git a/nixos/modules/services/networking/ntopng.nix b/nixos/modules/services/networking/ntopng.nix
index 022fc923edaa..e6344d7ff3b3 100644
--- a/nixos/modules/services/networking/ntopng.nix
+++ b/nixos/modules/services/networking/ntopng.nix
@@ -43,7 +43,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable ntopng, a high-speed web-based traffic analysis and flow
           collection tool.
 
@@ -63,7 +63,7 @@ in
         default = [ "any" ];
         example = [ "eth0" "wlan0" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of interfaces to monitor. Use "any" to monitor all interfaces.
         '';
       };
@@ -71,7 +71,7 @@ in
       httpPort = mkOption {
         default = 3000;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Sets the HTTP port of the embedded web server.
         '';
       };
@@ -79,7 +79,7 @@ in
       redis.address = mkOption {
         type = types.str;
         example = literalExpression "config.services.redis.ntopng.unixSocket";
-        description = ''
+        description = lib.mdDoc ''
           Redis address - may be a Unix socket or a network host and port.
         '';
       };
@@ -87,10 +87,10 @@ in
       redis.createInstance = mkOption {
         type = types.nullOr types.str;
         default = if versionAtLeast config.system.stateVersion "22.05" then "ntopng" else "";
-        description = ''
-          Local Redis instance name. Set to <literal>null</literal> to disable
-          local Redis instance. Defaults to <literal>""</literal> for
-          <literal>system.stateVersion</literal> older than 22.05.
+        description = lib.mdDoc ''
+          Local Redis instance name. Set to `null` to disable
+          local Redis instance. Defaults to `""` for
+          `system.stateVersion` older than 22.05.
         '';
       };
 
@@ -102,7 +102,7 @@ in
           --disable-login
         '';
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Overridable configuration file contents to use for ntopng. By
           default, use the contents automatically generated by NixOS.
         '';
@@ -111,10 +111,10 @@ in
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Configuration lines that will be appended to the generated ntopng
           configuration file. Note that this mechanism does not work when the
-          manual <option>configText</option> option is used.
+          manual {option}`configText` option is used.
         '';
       };
 
diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index 34728455a212..7e3bb565d10b 100644
--- a/nixos/modules/services/networking/ntp/chrony.nix
+++ b/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 = {
@@ -35,7 +35,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to synchronise your machine's time using chrony.
           Make sure you disable NTP if you enable this service.
         '';
@@ -45,7 +45,7 @@ in
         type = types.package;
         default = pkgs.chrony;
         defaultText = literalExpression "pkgs.chrony";
-        description = ''
+        description = lib.mdDoc ''
           Which chrony package to use.
         '';
       };
@@ -54,7 +54,7 @@ in
         default = config.networking.timeServers;
         defaultText = literalExpression "config.networking.timeServers";
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The set of NTP servers from which to synchronise.
         '';
       };
@@ -62,7 +62,7 @@ in
       serverOption = mkOption {
         default = "iburst";
         type = types.enum [ "iburst" "offline" ];
-        description = ''
+        description = lib.mdDoc ''
           Set option for server directives.
 
           Use "iburst" to rapidly poll on startup. Recommended if your machine
@@ -76,7 +76,7 @@ in
       enableNTS = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Network Time Security authentication.
           Make sure it is supported by your selected NTP server(s).
         '';
@@ -86,7 +86,7 @@ in
         enabled = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Allow chronyd to make a rapid measurement of the system clock error
             at boot time, and to correct the system clock by stepping before
             normal operation begins.
@@ -96,7 +96,7 @@ in
         threshold = mkOption {
           type = types.either types.float types.int;
           default = 1000; # by default, same threshold as 'ntpd -g' (1000s)
-          description = ''
+          description = lib.mdDoc ''
             The threshold of system clock error (in seconds) above which the
             clock will be stepped. If the correction required is less than the
             threshold, a slew is used instead.
@@ -107,15 +107,15 @@ in
       directory = mkOption {
         type = types.str;
         default = "/var/lib/chrony";
-        description = "Directory where chrony state is stored.";
+        description = lib.mdDoc "Directory where chrony state is stored.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration directives that should be added to
-          <literal>chrony.conf</literal>
+          `chrony.conf`
         '';
       };
 
@@ -123,7 +123,7 @@ in
         default = [];
         example = [ "-s" ];
         type = types.listOf types.str;
-        description = "Extra flags passed to the chronyd command.";
+        description = lib.mdDoc "Extra flags passed to the chronyd command.";
       };
     };
   };
@@ -166,7 +166,7 @@ in
         unitConfig.ConditionCapability = "CAP_SYS_TIME";
         serviceConfig =
           { Type = "simple";
-            ExecStart = "${chronyPkg}/bin/chronyd ${chronyFlags}";
+            ExecStart = "${chronyPkg}/bin/chronyd ${builtins.toString chronyFlags}";
 
             ProtectHome = "yes";
             ProtectSystem = "full";
diff --git a/nixos/modules/services/networking/ntp/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix
index 12be0d045a85..036a8df635db 100644
--- a/nixos/modules/services/networking/ntp/ntpd.nix
+++ b/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
 
@@ -40,21 +40,19 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to synchronise your machine's time using ntpd, as a peer in
           the NTP network.
-          </para>
-          <para>
-          Disables <literal>systemd.timesyncd</literal> if enabled.
+
+          Disables `systemd.timesyncd` if enabled.
         '';
       };
 
       restrictDefault = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The restriction flags to be set by default.
-          </para>
-          <para>
+
           The default flags prevent external hosts from using ntpd as a DDoS
           reflector, setting system time, and querying OS/ntpd version. As
           recommended in section 6.5.1.1.3, answer "No" of
@@ -65,10 +63,9 @@ in
 
       restrictSource = mkOption {
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The restriction flags to be set on source.
-          </para>
-          <para>
+
           The default flags allow peers to be added by ntpd from configured
           pool(s), but not by other means.
         '';
@@ -79,7 +76,7 @@ in
         default = config.networking.timeServers;
         defaultText = literalExpression "config.networking.timeServers";
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The set of NTP servers from which to synchronise.
         '';
       };
@@ -90,14 +87,14 @@ in
         example = ''
           fudge 127.127.1.0 stratum 10
         '';
-        description = ''
-          Additional text appended to <filename>ntp.conf</filename>.
+        description = lib.mdDoc ''
+          Additional text appended to {file}`ntp.conf`.
         '';
       };
 
       extraFlags = mkOption {
         type = types.listOf types.str;
-        description = "Extra flags passed to the ntpd command.";
+        description = lib.mdDoc "Extra flags passed to the ntpd command.";
         example = literalExpression ''[ "--interface=eth0" ]'';
         default = [];
       };
@@ -140,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/nixos/modules/services/networking/ntp/openntpd.nix b/nixos/modules/services/networking/ntp/openntpd.nix
index e86b71291f96..05df1f6e6266 100644
--- a/nixos/modules/services/networking/ntp/openntpd.nix
+++ b/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;
@@ -35,8 +35,8 @@ in
         listen on 127.0.0.1
         listen on ::1
       '';
-      description = ''
-        Additional text appended to <filename>openntpd.conf</filename>.
+      description = lib.mdDoc ''
+        Additional text appended to {file}`openntpd.conf`.
       '';
     };
 
@@ -44,7 +44,7 @@ in
       type = with types; separatedString " ";
       default = "";
       example = "-s";
-      description = ''
+      description = lib.mdDoc ''
         Extra options used when launching openntpd.
       '';
     };
diff --git a/nixos/modules/services/networking/nullidentdmod.nix b/nixos/modules/services/networking/nullidentdmod.nix
index b0d338a27941..e74e1dd6b795 100644
--- a/nixos/modules/services/networking/nullidentdmod.nix
+++ b/nixos/modules/services/networking/nullidentdmod.nix
@@ -3,11 +3,11 @@
 
 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;
-      description = "User ID to return. Set to null to return a random string each time.";
+      description = lib.mdDoc "User ID to return. Set to null to return a random string each time.";
       default = null;
       example = "alice";
     };
diff --git a/nixos/modules/services/networking/nylon.nix b/nixos/modules/services/networking/nylon.nix
index a20fa615af80..401dbe97c52d 100644
--- a/nixos/modules/services/networking/nylon.nix
+++ b/nixos/modules/services/networking/nylon.nix
@@ -29,7 +29,7 @@ let
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enables nylon as a running service upon activation.
         '';
       };
@@ -37,13 +37,13 @@ let
       name = mkOption {
         type = types.str;
         default = "";
-        description = "The name of this nylon instance.";
+        description = lib.mdDoc "The name of this nylon instance.";
       };
 
       nrConnections = mkOption {
         type = types.int;
         default = 10;
-        description = ''
+        description = lib.mdDoc ''
           The number of allowed simultaneous connections to the daemon, default 10.
         '';
       };
@@ -51,7 +51,7 @@ let
       logging = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable logging, default is no logging.
         '';
       };
@@ -59,7 +59,7 @@ let
       verbosity = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable verbose output, default is to not be verbose.
         '';
       };
@@ -67,7 +67,7 @@ let
       acceptInterface = mkOption {
         type = types.str;
         default = "lo";
-        description = ''
+        description = lib.mdDoc ''
           Tell nylon which interface to listen for client requests on, default is "lo".
         '';
       };
@@ -75,15 +75,15 @@ let
       bindInterface = mkOption {
         type = types.str;
         default = "enp3s0f0";
-        description = ''
+        description = lib.mdDoc ''
           Tell nylon which interface to use as an uplink, default is "enp3s0f0".
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 1080;
-        description = ''
+        description = lib.mdDoc ''
           What port to listen for client requests, default is 1080.
         '';
       };
@@ -91,7 +91,7 @@ let
       allowedIPRanges = mkOption {
         type = with types; listOf str;
         default = [ "192.168.0.0/16" "127.0.0.1/8" "172.16.0.1/12" "10.0.0.0/8" ];
-        description = ''
+        description = lib.mdDoc ''
            Allowed client IP ranges are evaluated first, defaults to ARIN IPv4 private ranges:
              [ "192.168.0.0/16" "127.0.0.0/8" "172.16.0.0/12" "10.0.0.0/8" ]
         '';
@@ -100,7 +100,7 @@ let
       deniedIPRanges = mkOption {
         type = with types; listOf str;
         default = [ "0.0.0.0/0" ];
-        description = ''
+        description = lib.mdDoc ''
           Denied client IP ranges, these gets evaluated after the allowed IP ranges, defaults to all IPv4 addresses:
             [ "0.0.0.0/0" ]
           To block all other access than the allowed.
@@ -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/nixos/modules/services/networking/ocserv.nix b/nixos/modules/services/networking/ocserv.nix
index dc26ffeafeef..9548fd92dbda 100644
--- a/nixos/modules/services/networking/ocserv.nix
+++ b/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/nixos/modules/services/networking/ofono.nix b/nixos/modules/services/networking/ofono.nix
index 460b06443c41..960fc35a70ac 100644
--- a/nixos/modules/services/networking/ofono.nix
+++ b/nixos/modules/services/networking/ofono.nix
@@ -19,13 +19,13 @@ in
   ###### interface
   options = {
     services.ofono = {
-      enable = mkEnableOption "Ofono";
+      enable = mkEnableOption (lib.mdDoc "Ofono");
 
       plugins = mkOption {
         type = types.listOf types.package;
         default = [];
         example = literalExpression "[ pkgs.modem-manager-gui ]";
-        description = ''
+        description = lib.mdDoc ''
           The list of plugins to install.
         '';
       };
diff --git a/nixos/modules/services/networking/oidentd.nix b/nixos/modules/services/networking/oidentd.nix
index feb84806ba99..7c7883c94611 100644
--- a/nixos/modules/services/networking/oidentd.nix
+++ b/nixos/modules/services/networking/oidentd.nix
@@ -11,7 +11,7 @@ with lib;
     services.oidentd.enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable ‘oidentd’, an implementation of the Ident
         protocol (RFC 1413).  It allows remote systems to identify the
         name of the user associated with a TCP connection.
diff --git a/nixos/modules/services/networking/onedrive.nix b/nixos/modules/services/networking/onedrive.nix
index 0256a6a41115..d782ec05352b 100644
--- a/nixos/modules/services/networking/onedrive.nix
+++ b/nixos/modules/services/networking/onedrive.nix
@@ -26,17 +26,13 @@ in {
   ### Interface
 
   options.services.onedrive = {
-    enable = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = "Enable OneDrive service";
-    };
+     enable = lib.mkEnableOption (lib.mdDoc "OneDrive service");
 
      package = lib.mkOption {
        type = lib.types.package;
        default = pkgs.onedrive;
        defaultText = lib.literalExpression "pkgs.onedrive";
-       description = ''
+       description = lib.mdDoc ''
          OneDrive package to use.
        '';
      };
diff --git a/nixos/modules/services/networking/openconnect.nix b/nixos/modules/services/networking/openconnect.nix
index de4b505130eb..469f0a3bc3bb 100644
--- a/nixos/modules/services/networking/openconnect.nix
+++ b/nixos/modules/services/networking/openconnect.nix
@@ -9,21 +9,27 @@ let
   };
   interfaceOptions = {
     options = {
+      autoStart = mkOption {
+        default = true;
+        description = lib.mdDoc "Whether this VPN connection should be started automatically.";
+        type = types.bool;
+      };
+
       gateway = mkOption {
-        description = "Gateway server to connect to.";
+        description = lib.mdDoc "Gateway server to connect to.";
         example = "gateway.example.com";
         type = types.str;
       };
 
       protocol = mkOption {
-        description = "Protocol to use.";
+        description = lib.mdDoc "Protocol to use.";
         example = "anyconnect";
         type =
           types.enum [ "anyconnect" "array" "nc" "pulse" "gp" "f5" "fortinet" ];
       };
 
       user = mkOption {
-        description = "Username to authenticate with.";
+        description = lib.mdDoc "Username to authenticate with.";
         example = "example-user";
         type = types.nullOr types.str;
       };
@@ -32,10 +38,10 @@ let
       # set an authentication cookie, because they have to be requested
       # for every new connection and would only work once.
       passwordFile = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           File containing the password to authenticate with. This
-          is passed to <code>openconnect</code> via the
-          <code>--passwd-on-stdin</code> option.
+          is passed to `openconnect` via the
+          `--passwd-on-stdin` option.
         '';
         default = null;
         example = "/var/lib/secrets/openconnect-passwd";
@@ -43,27 +49,27 @@ let
       };
 
       certificate = mkOption {
-        description = "Certificate to authenticate with.";
+        description = lib.mdDoc "Certificate to authenticate with.";
         default = null;
         example = "/var/lib/secrets/openconnect_certificate.pem";
         type = with types; nullOr (either path pkcs11);
       };
 
       privateKey = mkOption {
-        description = "Private key to authenticate with.";
+        description = lib.mdDoc "Private key to authenticate with.";
         example = "/var/lib/secrets/openconnect_private_key.pem";
         default = null;
         type = with types; nullOr (either path pkcs11);
       };
 
       extraOptions = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Extra config to be appended to the interface config. It should
           contain long-format options as would be accepted on the command
-          line by <code>openconnect</code>
+          line by `openconnect`
           (see https://www.infradead.org/openconnect/manual.html).
-          Non-key-value options like <code>deflate</code> can be used by
-          declaring them as booleans, i. e. <code>deflate = true;</code>.
+          Non-key-value options like `deflate` can be used by
+          declaring them as booleans, i. e. `deflate = true;`.
         '';
         default = { };
         example = {
@@ -95,7 +101,7 @@ let
     description = "OpenConnect Interface - ${name}";
     requires = [ "network-online.target" ];
     after = [ "network.target" "network-online.target" ];
-    wantedBy = [ "multi-user.target" ];
+    wantedBy = optional icfg.autoStart "multi-user.target";
 
     serviceConfig = {
       Type = "simple";
@@ -112,7 +118,7 @@ in {
     package = mkPackageOption pkgs "openconnect" { };
 
     interfaces = mkOption {
-      description = "OpenConnect interfaces.";
+      description = lib.mdDoc "OpenConnect interfaces.";
       default = { };
       example = {
         openconnect0 = {
diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index cf3f79fc578f..492a0936fdbb 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -115,12 +115,12 @@ in
         }
       '';
 
-      description = ''
+      description = lib.mdDoc ''
         Each attribute of this option defines a systemd service that
         runs an OpenVPN instance.  These can be OpenVPN servers or
         clients.  The name of each systemd service is
-        <literal>openvpn-<replaceable>name</replaceable>.service</literal>,
-        where <replaceable>name</replaceable> is the corresponding
+        `openvpn-«name».service`,
+        where «name» is the corresponding
         attribute name.
       '';
 
@@ -130,20 +130,20 @@ in
 
           config = mkOption {
             type = types.lines;
-            description = ''
+            description = lib.mdDoc ''
               Configuration of this OpenVPN instance.  See
-              <citerefentry><refentrytitle>openvpn</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+              {manpage}`openvpn(8)`
               for details.
 
               To import an external config file, use the following definition:
-              <literal>config = "config /path/to/config.ovpn"</literal>
+              `config = "config /path/to/config.ovpn"`
             '';
           };
 
           up = mkOption {
             default = "";
             type = types.lines;
-            description = ''
+            description = lib.mdDoc ''
               Shell commands executed when the instance is starting.
             '';
           };
@@ -151,7 +151,7 @@ in
           down = mkOption {
             default = "";
             type = types.lines;
-            description = ''
+            description = lib.mdDoc ''
               Shell commands executed when the instance is shutting down.
             '';
           };
@@ -159,13 +159,13 @@ in
           autoStart = mkOption {
             default = true;
             type = types.bool;
-            description = "Whether this OpenVPN instance should be started automatically.";
+            description = lib.mdDoc "Whether this OpenVPN instance should be started automatically.";
           };
 
           updateResolvConf = mkOption {
             default = false;
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Use the script from the update-resolv-conf package to automatically
               update resolv.conf with the DNS information provided by openvpn. The
               script will be run after the "up" commands and before the "down" commands.
@@ -174,7 +174,7 @@ in
 
           authUserPass = mkOption {
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               This option can be used to store the username / password credentials
               with the "auth-user-pass" authentication method.
 
@@ -184,12 +184,12 @@ in
 
               options = {
                 username = mkOption {
-                  description = "The username to store inside the credentials file.";
+                  description = lib.mdDoc "The username to store inside the credentials file.";
                   type = types.str;
                 };
 
                 password = mkOption {
-                  description = "The password to store inside the credentials file.";
+                  description = lib.mdDoc "The password to store inside the credentials file.";
                   type = types.str;
                 };
               };
diff --git a/nixos/modules/services/networking/ostinato.nix b/nixos/modules/services/networking/ostinato.nix
index 4da11984b9fc..dc07313ea901 100644
--- a/nixos/modules/services/networking/ostinato.nix
+++ b/nixos/modules/services/networking/ostinato.nix
@@ -26,12 +26,12 @@ 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 = ''
+        description = lib.mdDoc ''
           Port to listen on.
         '';
       };
@@ -39,7 +39,7 @@ in
       rateAccuracy = mkOption {
         type = types.enum [ "High" "Low" ];
         default = "High";
-        description = ''
+        description = lib.mdDoc ''
           To ensure that the actual transmit rate is as close as possible to
           the configured transmit rate, Drone runs a busy-wait loop.
           While this provides the maximum accuracy possible, the CPU
@@ -52,9 +52,9 @@ in
         address = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = ''
+          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 ::
           '';
@@ -66,7 +66,7 @@ in
           type = types.listOf types.str;
           default = [];
           example = [ "eth*" "lo*" ];
-          description = ''
+          description = lib.mdDoc ''
             For a port to pass the filter and appear on the port list managed
             by drone, it be allowed by this include list.
           '';
@@ -75,7 +75,7 @@ in
           type = types.listOf types.str;
           default = [];
           example = [ "usbmon*" "eth0" ];
-          description = ''
+          description = lib.mdDoc ''
             A list of ports does not appear on the port list managed by drone.
           '';
         };
diff --git a/nixos/modules/services/networking/owamp.nix b/nixos/modules/services/networking/owamp.nix
index baf64347b099..32b2dab9e3c7 100644
--- a/nixos/modules/services/networking/owamp.nix
+++ b/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/nixos/modules/services/networking/pdns-recursor.nix b/nixos/modules/services/networking/pdns-recursor.nix
index a986f83141c4..2f07cefc736e 100644
--- a/nixos/modules/services/networking/pdns-recursor.nix
+++ b/nixos/modules/services/networking/pdns-recursor.nix
@@ -27,20 +27,20 @@ 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;
       default = [ "::" "0.0.0.0" ];
-      description = ''
+      description = lib.mdDoc ''
         IP addresses Recursor DNS server will bind to.
       '';
     };
 
     dns.port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 53;
-      description = ''
+      description = lib.mdDoc ''
         Port number Recursor DNS server will bind to.
       '';
     };
@@ -53,7 +53,7 @@ in {
         "::1/128" "fc00::/7" "fe80::/10"
       ];
       example = [ "0.0.0.0/0" "::/0" ];
-      description = ''
+      description = lib.mdDoc ''
         IP address ranges of clients allowed to make DNS queries.
       '';
     };
@@ -61,15 +61,15 @@ in {
     api.address = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = ''
+      description = lib.mdDoc ''
         IP address Recursor REST API server will bind to.
       '';
     };
 
     api.port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8082;
-      description = ''
+      description = lib.mdDoc ''
         Port number Recursor REST API server will bind to.
       '';
     };
@@ -78,7 +78,7 @@ in {
       type = types.listOf types.str;
       default = [ "127.0.0.1" "::1" ];
       example = [ "0.0.0.0/0" "::/0" ];
-      description = ''
+      description = lib.mdDoc ''
         IP address ranges of clients allowed to make API requests.
       '';
     };
@@ -86,7 +86,7 @@ in {
     exportHosts = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
        Whether to export names and IP addresses defined in /etc/hosts.
       '';
     };
@@ -94,7 +94,7 @@ in {
     forwardZones = mkOption {
       type = types.attrs;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         DNS zones to be forwarded to other authoritative servers.
       '';
     };
@@ -103,7 +103,7 @@ in {
       type = types.attrs;
       example = { eth = "[::1]:5353"; };
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         DNS zones to be forwarded to other recursive servers.
       '';
     };
@@ -111,7 +111,7 @@ in {
     dnssecValidation = mkOption {
       type = types.enum ["off" "process-no-validate" "process" "log-fail" "validate"];
       default = "validate";
-      description = ''
+      description = lib.mdDoc ''
         Controls the level of DNSSEC processing done by the PowerDNS Recursor.
         See https://doc.powerdns.com/md/recursor/dnssec/ for a detailed explanation.
       '';
@@ -120,11 +120,11 @@ in {
     serveRFC1918 = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to directly resolve the RFC1918 reverse-mapping domains:
-        <literal>10.in-addr.arpa</literal>,
-        <literal>168.192.in-addr.arpa</literal>,
-        <literal>16-31.172.in-addr.arpa</literal>
+        `10.in-addr.arpa`,
+        `168.192.in-addr.arpa`,
+        `16-31.172.in-addr.arpa`
         This saves load on the AS112 servers.
       '';
     };
@@ -138,11 +138,11 @@ in {
           log-common-errors = true;
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         PowerDNS Recursor settings. Use this option to configure Recursor
         settings not exposed in a NixOS option or to bypass one.
         See the full documentation at
-        <link xlink:href="https://doc.powerdns.com/recursor/settings.html"/>
+        <https://doc.powerdns.com/recursor/settings.html>
         for the available options.
       '';
     };
@@ -150,9 +150,9 @@ in {
     luaConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         The content Lua configuration file for PowerDNS Recursor. See
-        <link xlink:href="https://doc.powerdns.com/recursor/lua-config/index.html"/>.
+        <https://doc.powerdns.com/recursor/lua-config/index.html>.
       '';
     };
   };
diff --git a/nixos/modules/services/networking/pdnsd.nix b/nixos/modules/services/networking/pdnsd.nix
index 24b5bbc5104e..8fe27a44eee6 100644
--- a/nixos/modules/services/networking/pdnsd.nix
+++ b/nixos/modules/services/networking/pdnsd.nix
@@ -24,38 +24,38 @@ in
 
 { options =
     { services.pdnsd =
-        { enable = mkEnableOption "pdnsd";
+        { enable = mkEnableOption (lib.mdDoc "pdnsd");
 
           cacheDir = mkOption {
             type = types.str;
             default = "/var/cache/pdnsd";
-            description = "Directory holding the pdnsd cache";
+            description = lib.mdDoc "Directory holding the pdnsd cache";
           };
 
           globalConfig = mkOption {
             type = types.lines;
             default = "";
-            description = ''
+            description = lib.mdDoc ''
               Global configuration that should be added to the global directory
-              of <literal>pdnsd.conf</literal>.
+              of `pdnsd.conf`.
             '';
           };
 
           serverConfig = mkOption {
             type = types.lines;
             default = "";
-            description = ''
+            description = lib.mdDoc ''
               Server configuration that should be added to the server directory
-              of <literal>pdnsd.conf</literal>.
+              of `pdnsd.conf`.
             '';
           };
 
           extraConfig = mkOption {
             type = types.lines;
             default = "";
-            description = ''
+            description = lib.mdDoc ''
               Extra configuration directives that should be added to
-              <literal>pdnsd.conf</literal>.
+              `pdnsd.conf`.
             '';
           };
         };
diff --git a/nixos/modules/services/networking/pixiecore.nix b/nixos/modules/services/networking/pixiecore.nix
index d2642c82c2d3..ea4008d4d515 100644
--- a/nixos/modules/services/networking/pixiecore.nix
+++ b/nixos/modules/services/networking/pixiecore.nix
@@ -10,18 +10,18 @@ in
 
   options = {
     services.pixiecore = {
-      enable = mkEnableOption "Pixiecore";
+      enable = mkEnableOption (lib.mdDoc "Pixiecore");
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports (67, 69 UDP and 4011, 'port', 'statusPort' TCP) in the firewall for Pixiecore.
         '';
       };
 
       mode = mkOption {
-        description = "Which mode to use";
+        description = lib.mdDoc "Which mode to use";
         default = "boot";
         type = types.enum [ "api" "boot" ];
       };
@@ -29,61 +29,61 @@ in
       debug = mkOption {
         type = types.bool;
         default = false;
-        description = "Log more things that aren't directly related to booting a recognized client";
+        description = lib.mdDoc "Log more things that aren't directly related to booting a recognized client";
       };
 
       dhcpNoBind = mkOption {
         type = types.bool;
         default = false;
-        description = "Handle DHCP traffic without binding to the DHCP server port";
+        description = lib.mdDoc "Handle DHCP traffic without binding to the DHCP server port";
       };
 
       kernel = mkOption {
         type = types.str or types.path;
         default = "";
-        description = "Kernel path. Ignored unless mode is set to 'boot'";
+        description = lib.mdDoc "Kernel path. Ignored unless mode is set to 'boot'";
       };
 
       initrd = mkOption {
         type = types.str or types.path;
         default = "";
-        description = "Initrd path. Ignored unless mode is set to 'boot'";
+        description = lib.mdDoc "Initrd path. Ignored unless mode is set to 'boot'";
       };
 
       cmdLine = mkOption {
         type = types.str;
         default = "";
-        description = "Kernel commandline arguments. Ignored unless mode is set to 'boot'";
+        description = lib.mdDoc "Kernel commandline arguments. Ignored unless mode is set to 'boot'";
       };
 
       listen = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "IPv4 address to listen on";
+        description = lib.mdDoc "IPv4 address to listen on";
       };
 
       port = mkOption {
         type = types.port;
         default = 80;
-        description = "Port to listen on for HTTP";
+        description = lib.mdDoc "Port to listen on for HTTP";
       };
 
       statusPort = mkOption {
         type = types.port;
         default = 80;
-        description = "HTTP port for status information (can be the same as --port)";
+        description = lib.mdDoc "HTTP port for status information (can be the same as --port)";
       };
 
       apiServer = mkOption {
         type = types.str;
         example = "localhost:8080";
-        description = "host:port to connect to the API. Ignored unless mode is set to 'api'";
+        description = lib.mdDoc "host:port to connect to the API. Ignored unless mode is set to 'api'";
       };
 
       extraArguments = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Additional command line arguments to pass to Pixiecore";
+        description = lib.mdDoc "Additional command line arguments to pass to Pixiecore";
       };
     };
   };
diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix
index c6d4c14dcb7e..188598ea7b86 100644
--- a/nixos/modules/services/networking/pleroma.nix
+++ b/nixos/modules/services/networking/pleroma.nix
@@ -1,41 +1,40 @@
 { config, options, lib, pkgs, stdenv, ... }:
 let
   cfg = config.services.pleroma;
-  cookieFile = "/var/lib/pleroma/.cookie";
 in {
   options = {
     services.pleroma = with lib; {
-      enable = mkEnableOption "pleroma";
+      enable = mkEnableOption (lib.mdDoc "pleroma");
 
       package = mkOption {
         type = types.package;
-        default = pkgs.pleroma.override { inherit cookieFile; };
+        default = pkgs.pleroma;
         defaultText = literalExpression "pkgs.pleroma";
-        description = "Pleroma package to use.";
+        description = lib.mdDoc "Pleroma package to use.";
       };
 
       user = mkOption {
         type = types.str;
         default = "pleroma";
-        description = "User account under which pleroma runs.";
+        description = lib.mdDoc "User account under which pleroma runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "pleroma";
-        description = "Group account under which pleroma runs.";
+        description = lib.mdDoc "Group account under which pleroma runs.";
       };
 
       stateDir = mkOption {
         type = types.str;
         default = "/var/lib/pleroma";
         readOnly = true;
-        description = "Directory where the pleroma service will save the uploads and static files.";
+        description = lib.mdDoc "Directory where the pleroma service will save the uploads and static files.";
       };
 
       configs = mkOption {
         type = with types; listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Pleroma public configuration.
 
           This list gets appended from left to
@@ -43,9 +42,9 @@ in {
           configuration imperatively, meaning you can override a
           setting by appending a new str to this NixOS option list.
 
-          <emphasis>DO NOT STORE ANY PLEROMA SECRET
-          HERE</emphasis>, use
-          <link linkend="opt-services.pleroma.secretConfigFile">services.pleroma.secretConfigFile</link>
+          *DO NOT STORE ANY PLEROMA SECRET
+          HERE*, use
+          [services.pleroma.secretConfigFile](#opt-services.pleroma.secretConfigFile)
           instead.
 
           This setting is going to be stored in a file part of
@@ -53,18 +52,18 @@ in {
           the right place to store any secret
 
           Have a look to Pleroma section in the NixOS manual for more
-          informations.
+          information.
           '';
       };
 
       secretConfigFile = mkOption {
         type = types.str;
         default = "/var/lib/pleroma/secrets.exs";
-        description = ''
+        description = lib.mdDoc ''
           Path to the file containing your secret pleroma configuration.
 
-          <emphasis>DO NOT POINT THIS OPTION TO THE NIX
-          STORE</emphasis>, the store being world-readable, it'll
+          *DO NOT POINT THIS OPTION TO THE NIX
+          STORE*, the store being world-readable, it'll
           compromise all your secrets.
         '';
       };
@@ -101,6 +100,7 @@ in {
       after = [ "network-online.target" "postgresql.service" ];
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ config.environment.etc."/pleroma/config.exs".source ];
+      environment.RELEASE_COOKIE = "/var/lib/pleroma/.cookie";
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
@@ -118,10 +118,10 @@ in {
         # Better be safe than sorry migration-wise.
         ExecStartPre =
           let preScript = pkgs.writers.writeBashBin "pleromaStartPre" ''
-            if [ ! -f "${cookieFile}" ] || [ ! -s "${cookieFile}" ]
+            if [ ! -f /var/lib/pleroma/.cookie ]
             then
               echo "Creating cookie file"
-              dd if=/dev/urandom bs=1 count=16 | ${pkgs.hexdump}/bin/hexdump -e '16/1 "%02x"' > "${cookieFile}"
+              dd if=/dev/urandom bs=1 count=16 | hexdump -e '16/1 "%02x"' > /var/lib/pleroma/.cookie
             fi
             ${cfg.package}/bin/pleroma_ctl migrate
           '';
diff --git a/nixos/modules/services/networking/polipo.nix b/nixos/modules/services/networking/polipo.nix
index 1ff9388346b6..8581553829bf 100644
--- a/nixos/modules/services/networking/polipo.nix
+++ b/nixos/modules/services/networking/polipo.nix
@@ -23,29 +23,25 @@ in
 
     services.polipo = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to run the polipo caching web proxy.";
-      };
+      enable = mkEnableOption (lib.mdDoc "polipo caching web proxy");
 
       proxyAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "IP address on which Polipo will listen.";
+        description = lib.mdDoc "IP address on which Polipo will listen.";
       };
 
       proxyPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8123;
-        description = "TCP port on which Polipo will listen.";
+        description = lib.mdDoc "TCP port on which Polipo will listen.";
       };
 
       allowedClients = mkOption {
         type = types.listOf types.str;
         default = [ "127.0.0.1" "::1" ];
         example = [ "127.0.0.1" "::1" "134.157.168.0/24" "2001:660:116::/48" ];
-        description = ''
+        description = lib.mdDoc ''
           List of IP addresses or network addresses that may connect to Polipo.
         '';
       };
@@ -54,7 +50,7 @@ in
         type = types.str;
         default = "";
         example = "localhost:8124";
-        description = ''
+        description = lib.mdDoc ''
           Hostname and port number of an HTTP parent proxy;
           it should have the form ‘host:port’.
         '';
@@ -64,7 +60,7 @@ in
         type = types.str;
         default = "";
         example = "localhost:9050";
-        description = ''
+        description = lib.mdDoc ''
           Hostname and port number of an SOCKS parent proxy;
           it should have the form ‘host:port’.
         '';
@@ -73,7 +69,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Polio configuration. Contents will be added
           verbatim to the configuration file.
         '';
diff --git a/nixos/modules/services/networking/powerdns.nix b/nixos/modules/services/networking/powerdns.nix
index b035698456c0..6aa5928d6370 100644
--- a/nixos/modules/services/networking/powerdns.nix
+++ b/nixos/modules/services/networking/powerdns.nix
@@ -8,14 +8,14 @@ let
 in {
   options = {
     services.powerdns = {
-      enable = mkEnableOption "PowerDNS domain name server";
+      enable = mkEnableOption (lib.mdDoc "PowerDNS domain name server");
 
       extraConfig = mkOption {
         type = types.lines;
         default = "launch=bind";
-        description = ''
+        description = lib.mdDoc ''
           PowerDNS configuration. Refer to
-          <link xlink:href="https://doc.powerdns.com/authoritative/settings.html"/>
+          <https://doc.powerdns.com/authoritative/settings.html>
           for details on supported values.
         '';
       };
diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix
index d1ed25b0238f..75fc04c67571 100644
--- a/nixos/modules/services/networking/pppd.nix
+++ b/nixos/modules/services/networking/pppd.nix
@@ -12,18 +12,18 @@ in
 
   options = {
     services.pppd = {
-      enable = mkEnableOption "pppd";
+      enable = mkEnableOption (lib.mdDoc "pppd");
 
       package = mkOption {
         default = pkgs.ppp;
         defaultText = literalExpression "pkgs.ppp";
         type = types.package;
-        description = "pppd package to use.";
+        description = lib.mdDoc "pppd package to use.";
       };
 
       peers = mkOption {
         default = {};
-        description = "pppd peers.";
+        description = lib.mdDoc "pppd peers.";
         type = types.attrsOf (types.submodule (
           { name, ... }:
           {
@@ -32,27 +32,27 @@ in
                 type = types.str;
                 default = name;
                 example = "dialup";
-                description = "Name of the PPP peer.";
+                description = lib.mdDoc "Name of the PPP peer.";
               };
 
               enable = mkOption {
                 type = types.bool;
                 default = true;
                 example = false;
-                description = "Whether to enable this PPP peer.";
+                description = lib.mdDoc "Whether to enable this PPP peer.";
               };
 
               autostart = mkOption {
                 type = types.bool;
                 default = true;
                 example = false;
-                description = "Whether the PPP session is automatically started at boot time.";
+                description = lib.mdDoc "Whether the PPP session is automatically started at boot time.";
               };
 
               config = mkOption {
                 type = types.lines;
                 default = "";
-                description = "pppd configuration for this peer, see the pppd(8) man page.";
+                description = lib.mdDoc "pppd configuration for this peer, see the pppd(8) man page.";
               };
             };
           }));
diff --git a/nixos/modules/services/networking/pptpd.nix b/nixos/modules/services/networking/pptpd.nix
index 3e7753b9dd35..703dda99803e 100644
--- a/nixos/modules/services/networking/pptpd.nix
+++ b/nixos/modules/services/networking/pptpd.nix
@@ -5,35 +5,35 @@ 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;
-        description = "The server-side IP address.";
+        description = lib.mdDoc "The server-side IP address.";
         default     = "10.124.124.1";
       };
 
       clientIpRange = mkOption {
         type        = types.str;
-        description = "The range from which client IPs are drawn.";
+        description = lib.mdDoc "The range from which client IPs are drawn.";
         default     = "10.124.124.2-11";
       };
 
       maxClients = mkOption {
         type        = types.int;
-        description = "The maximum number of simultaneous connections.";
+        description = lib.mdDoc "The maximum number of simultaneous connections.";
         default     = 10;
       };
 
       extraPptpdOptions = mkOption {
         type        = types.lines;
-        description = "Adds extra lines to the pptpd configuration file.";
+        description = lib.mdDoc "Adds extra lines to the pptpd configuration file.";
         default     = "";
       };
 
       extraPppdOptions = mkOption {
         type        = types.lines;
-        description = "Adds extra lines to the pppd options file.";
+        description = lib.mdDoc "Adds extra lines to the pppd options file.";
         default     = "";
         example     = ''
           ms-dns 8.8.8.8
@@ -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 \
@@ -108,7 +108,7 @@ with lib;
         #username	pptpd	password	*
         EOF
 
-        chown root.root "$secrets"
+        chown root:root "$secrets"
         chmod 600 "$secrets"
       '';
 
diff --git a/nixos/modules/services/networking/prayer.nix b/nixos/modules/services/networking/prayer.nix
index ae9258b27122..197aa8a6f448 100644
--- a/nixos/modules/services/networking/prayer.nix
+++ b/nixos/modules/services/networking/prayer.nix
@@ -41,12 +41,12 @@ in
 
     services.prayer = {
 
-      enable = mkEnableOption "the prayer webmail http server";
+      enable = mkEnableOption (lib.mdDoc "the prayer webmail http server");
 
       port = mkOption {
         default = 2080;
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           Port the prayer http server is listening to.
         '';
       };
@@ -54,7 +54,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "" ;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration. Contents will be added verbatim to the configuration file.
         '';
       };
@@ -82,7 +82,7 @@ in
       serviceConfig.Type = "forking";
       preStart = ''
         mkdir -m 0755 -p ${stateDir}
-        chown ${prayerUser}.${prayerGroup} ${stateDir}
+        chown ${prayerUser}:${prayerGroup} ${stateDir}
       '';
       script = "${prayer}/sbin/prayer --config-file=${prayerCfg}";
     };
diff --git a/nixos/modules/services/networking/privoxy.nix b/nixos/modules/services/networking/privoxy.nix
index 7bc964d5f34a..78d02aaa1125 100644
--- a/nixos/modules/services/networking/privoxy.nix
+++ b/nixos/modules/services/networking/privoxy.nix
@@ -53,12 +53,12 @@ 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;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to configure Privoxy to use Tor's faster SOCKS port,
         suitable for HTTP.
       '';
@@ -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,16 +98,18 @@ 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.
+        :::
       '';
     };
 
     userActions = mkOption {
       type = types.lines;
       default = "";
-      description = ''
-        Actions to be included in a <literal>user.action</literal> file. This
+      description = lib.mdDoc ''
+        Actions to be included in a `user.action` file. This
         will have a higher priority and can be used to override all other
         actions.
       '';
@@ -116,8 +118,8 @@ in
     userFilters = mkOption {
       type = types.lines;
       default = "";
-      description = ''
-        Filters to be included in a <literal>user.filter</literal> file. This
+      description = lib.mdDoc ''
+        Filters to be included in a `user.filter` file. This
         will have a higher priority and can be used to override all other
         filters definitions.
       '';
@@ -130,13 +132,13 @@ in
         options.listen-address = mkOption {
           type = types.str;
           default = "127.0.0.1:8118";
-          description = "Pair of address:port the proxy server is listening to.";
+          description = lib.mdDoc "Pair of address:port the proxy server is listening to.";
         };
 
         options.enable-edit-actions = mkOption {
           type = types.bool;
           default = false;
-          description = "Whether the web-based actions file editor may be used.";
+          description = lib.mdDoc "Whether the web-based actions file editor may be used.";
         };
 
         options.actionsfile = mkOption {
@@ -146,7 +148,7 @@ in
           apply = x: x ++ optional (cfg.userActions != "")
             (toString (pkgs.writeText "user.actions" cfg.userActions));
           default = [ "match-all.action" "default.action" ];
-          description = ''
+          description = lib.mdDoc ''
             List of paths to Privoxy action files. These paths may either be
             absolute or relative to the privoxy configuration directory.
           '';
@@ -157,7 +159,7 @@ in
           default = [ "default.filter" ];
           apply = x: x ++ optional (cfg.userFilters != "")
             (toString (pkgs.writeText "user.filter" cfg.userFilters));
-          description = ''
+          description = lib.mdDoc ''
             List of paths to Privoxy filter files. These paths may either be
             absolute or relative to the privoxy configuration directory.
           '';
@@ -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/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index 42596ccfefd9..342638f93bae 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -10,19 +10,19 @@ let
 
       key = mkOption {
         type = types.path;
-        description = "Path to the key file.";
+        description = lib.mdDoc "Path to the key file.";
       };
 
       # TODO: rename to certificate to match the prosody config
       cert = mkOption {
         type = types.path;
-        description = "Path to the certificate file.";
+        description = lib.mdDoc "Path to the certificate file.";
       };
 
       extraOptions = mkOption {
         type = types.attrs;
         default = {};
-        description = "Extra SSL configuration options.";
+        description = lib.mdDoc "Extra SSL configuration options.";
       };
 
     };
@@ -32,11 +32,11 @@ let
     options = {
       url = mkOption {
         type = types.str;
-        description = "URL of the endpoint you want to make discoverable";
+        description = lib.mdDoc "URL of the endpoint you want to make discoverable";
       };
       description = mkOption {
         type = types.str;
-        description = "A short description of the endpoint you want to advertise";
+        description = lib.mdDoc "A short description of the endpoint you want to advertise";
       };
     };
   };
@@ -46,216 +46,216 @@ let
     roster = mkOption {
       type = types.bool;
       default = true;
-      description = "Allow users to have a roster";
+      description = lib.mdDoc "Allow users to have a roster";
     };
 
     saslauth = mkOption {
       type = types.bool;
       default = true;
-      description = "Authentication for clients and servers. Recommended if you want to log in.";
+      description = lib.mdDoc "Authentication for clients and servers. Recommended if you want to log in.";
     };
 
     tls = mkOption {
       type = types.bool;
       default = true;
-      description = "Add support for secure TLS on c2s/s2s connections";
+      description = lib.mdDoc "Add support for secure TLS on c2s/s2s connections";
     };
 
     dialback = mkOption {
       type = types.bool;
       default = true;
-      description = "s2s dialback support";
+      description = lib.mdDoc "s2s dialback support";
     };
 
     disco = mkOption {
       type = types.bool;
       default = true;
-      description = "Service discovery";
+      description = lib.mdDoc "Service discovery";
     };
 
     # Not essential, but recommended
     carbons = mkOption {
       type = types.bool;
       default = true;
-      description = "Keep multiple clients in sync";
+      description = lib.mdDoc "Keep multiple clients in sync";
     };
 
     csi = mkOption {
       type = types.bool;
       default = true;
-      description = "Implements the CSI protocol that allows clients to report their active/inactive state to the server";
+      description = lib.mdDoc "Implements the CSI protocol that allows clients to report their active/inactive state to the server";
     };
 
     cloud_notify = mkOption {
       type = types.bool;
       default = true;
-      description = "Push notifications to inform users of new messages or other pertinent information even when they have no XMPP clients online";
+      description = lib.mdDoc "Push notifications to inform users of new messages or other pertinent information even when they have no XMPP clients online";
     };
 
     pep = mkOption {
       type = types.bool;
       default = true;
-      description = "Enables users to publish their mood, activity, playing music and more";
+      description = lib.mdDoc "Enables users to publish their mood, activity, playing music and more";
     };
 
     private = mkOption {
       type = types.bool;
       default = true;
-      description = "Private XML storage (for room bookmarks, etc.)";
+      description = lib.mdDoc "Private XML storage (for room bookmarks, etc.)";
     };
 
     blocklist = mkOption {
       type = types.bool;
       default = true;
-      description = "Allow users to block communications with other users";
+      description = lib.mdDoc "Allow users to block communications with other users";
     };
 
     vcard = mkOption {
       type = types.bool;
       default = false;
-      description = "Allow users to set vCards";
+      description = lib.mdDoc "Allow users to set vCards";
     };
 
     vcard_legacy = mkOption {
       type = types.bool;
       default = true;
-      description = "Converts users profiles and Avatars between old and new formats";
+      description = lib.mdDoc "Converts users profiles and Avatars between old and new formats";
     };
 
     bookmarks = mkOption {
       type = types.bool;
       default = true;
-      description = "Allows interop between older clients that use XEP-0048: Bookmarks in its 1.0 version and recent clients which use it in PEP";
+      description = lib.mdDoc "Allows interop between older clients that use XEP-0048: Bookmarks in its 1.0 version and recent clients which use it in PEP";
     };
 
     # Nice to have
     version = mkOption {
       type = types.bool;
       default = true;
-      description = "Replies to server version requests";
+      description = lib.mdDoc "Replies to server version requests";
     };
 
     uptime = mkOption {
       type = types.bool;
       default = true;
-      description = "Report how long server has been running";
+      description = lib.mdDoc "Report how long server has been running";
     };
 
     time = mkOption {
       type = types.bool;
       default = true;
-      description = "Let others know the time here on this server";
+      description = lib.mdDoc "Let others know the time here on this server";
     };
 
     ping = mkOption {
       type = types.bool;
       default = true;
-      description = "Replies to XMPP pings with pongs";
+      description = lib.mdDoc "Replies to XMPP pings with pongs";
     };
 
     register = mkOption {
       type = types.bool;
       default = true;
-      description = "Allow users to register on this server using a client and change passwords";
+      description = lib.mdDoc "Allow users to register on this server using a client and change passwords";
     };
 
     mam = mkOption {
       type = types.bool;
       default = true;
-      description = "Store messages in an archive and allow users to access it";
+      description = lib.mdDoc "Store messages in an archive and allow users to access it";
     };
 
     smacks = mkOption {
       type = types.bool;
       default = true;
-      description = "Allow a client to resume a disconnected session, and prevent message loss";
+      description = lib.mdDoc "Allow a client to resume a disconnected session, and prevent message loss";
     };
 
     # Admin interfaces
     admin_adhoc = mkOption {
       type = types.bool;
       default = true;
-      description = "Allows administration via an XMPP client that supports ad-hoc commands";
+      description = lib.mdDoc "Allows administration via an XMPP client that supports ad-hoc commands";
     };
 
     http_files = mkOption {
       type = types.bool;
       default = true;
-      description = "Serve static files from a directory over HTTP";
+      description = lib.mdDoc "Serve static files from a directory over HTTP";
     };
 
     proxy65 = mkOption {
       type = types.bool;
       default = true;
-      description = "Enables a file transfer proxy service which clients behind NAT can use";
+      description = lib.mdDoc "Enables a file transfer proxy service which clients behind NAT can use";
     };
 
     admin_telnet = mkOption {
       type = types.bool;
       default = false;
-      description = "Opens telnet console interface on localhost port 5582";
+      description = lib.mdDoc "Opens telnet console interface on localhost port 5582";
     };
 
     # HTTP modules
     bosh = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable BOSH clients, aka 'Jabber over HTTP'";
+      description = lib.mdDoc "Enable BOSH clients, aka 'Jabber over HTTP'";
     };
 
     websocket = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable WebSocket support";
+      description = lib.mdDoc "Enable WebSocket support";
     };
 
     # Other specific functionality
     limits = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable bandwidth limiting for XMPP connections";
+      description = lib.mdDoc "Enable bandwidth limiting for XMPP connections";
     };
 
     groups = mkOption {
       type = types.bool;
       default = false;
-      description = "Shared roster support";
+      description = lib.mdDoc "Shared roster support";
     };
 
     server_contact_info = mkOption {
       type = types.bool;
       default = false;
-      description = "Publish contact information for this service";
+      description = lib.mdDoc "Publish contact information for this service";
     };
 
     announce = mkOption {
       type = types.bool;
       default = false;
-      description = "Send announcement to all online users";
+      description = lib.mdDoc "Send announcement to all online users";
     };
 
     welcome = mkOption {
       type = types.bool;
       default = false;
-      description = "Welcome users who register accounts";
+      description = lib.mdDoc "Welcome users who register accounts";
     };
 
     watchregistrations = mkOption {
       type = types.bool;
       default = false;
-      description = "Alert admins of registrations";
+      description = lib.mdDoc "Alert admins of registrations";
     };
 
     motd = mkOption {
       type = types.bool;
       default = false;
-      description = "Send a message to users when they log in";
+      description = lib.mdDoc "Send a message to users when they log in";
     };
 
     legacyauth = mkOption {
       type = types.bool;
       default = false;
-      description = "Legacy authentication. Only used by some old clients and bots";
+      description = lib.mdDoc "Legacy authentication. Only used by some old clients and bots";
     };
   };
 
@@ -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: ''
@@ -279,27 +279,27 @@ let
     options = {
       domain = mkOption {
         type = types.str;
-        description = "Domain name of the MUC";
+        description = lib.mdDoc "Domain name of the MUC";
       };
       name = mkOption {
         type = types.str;
-        description = "The name to return in service discovery responses for the MUC service itself";
+        description = lib.mdDoc "The name to return in service discovery responses for the MUC service itself";
         default = "Prosody Chatrooms";
       };
       restrictRoomCreation = mkOption {
         type = types.enum [ true false "admin" "local" ];
         default = false;
-        description = "Restrict room creation to server admins";
+        description = lib.mdDoc "Restrict room creation to server admins";
       };
       maxHistoryMessages = mkOption {
         type = types.int;
         default = 20;
-        description = "Specifies a limit on what each room can be configured to keep";
+        description = lib.mdDoc "Specifies a limit on what each room can be configured to keep";
       };
       roomLocking = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enables room locking, which means that a room must be
           configured before it can be used. Locked rooms are invisible
           and cannot be entered by anyone but the creator
@@ -308,15 +308,15 @@ let
       roomLockTimeout = mkOption {
         type = types.int;
         default = 300;
-        description = ''
-          Timout after which the room is destroyed or unlocked if not
+        description = lib.mdDoc ''
+          Timeout after which the room is destroyed or unlocked if not
           configured, in seconds
        '';
       };
       tombstones = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           When a room is destroyed, it leaves behind a tombstone which
           prevents the room being entered or recreated. It also allows
           anyone who was not in the room at the time it was destroyed
@@ -329,7 +329,7 @@ let
       tombstoneExpiry = mkOption {
         type = types.int;
         default = 2678400;
-        description = ''
+        description = lib.mdDoc ''
           This settings controls how long a tombstone is considered
           valid. It defaults to 31 days. After this time, the room in
           question can be created again.
@@ -339,7 +339,7 @@ let
       vcard_muc = mkOption {
         type = types.bool;
         default = true;
-      description = "Adds the ability to set vCard for Multi User Chat rooms";
+      description = lib.mdDoc "Adds the ability to set vCard for Multi User Chat rooms";
       };
 
       # Extra parameters. Defaulting to prosody default values.
@@ -350,42 +350,42 @@ let
       roomDefaultPublic = mkOption {
         type = types.bool;
         default = true;
-        description = "If set, the MUC rooms will be public by default.";
+        description = lib.mdDoc "If set, the MUC rooms will be public by default.";
       };
       roomDefaultMembersOnly = mkOption {
         type = types.bool;
         default = false;
-        description = "If set, the MUC rooms will only be accessible to the members by default.";
+        description = lib.mdDoc "If set, the MUC rooms will only be accessible to the members by default.";
       };
       roomDefaultModerated = mkOption {
         type = types.bool;
         default = false;
-        description = "If set, the MUC rooms will be moderated by default.";
+        description = lib.mdDoc "If set, the MUC rooms will be moderated by default.";
       };
       roomDefaultPublicJids = mkOption {
         type = types.bool;
         default = false;
-        description = "If set, the MUC rooms will display the public JIDs by default.";
+        description = lib.mdDoc "If set, the MUC rooms will display the public JIDs by default.";
       };
       roomDefaultChangeSubject = mkOption {
         type = types.bool;
         default = false;
-        description = "If set, the rooms will display the public JIDs by default.";
+        description = lib.mdDoc "If set, the rooms will display the public JIDs by default.";
       };
       roomDefaultHistoryLength = mkOption {
         type = types.int;
         default = 20;
-        description = "Number of history message sent to participants by default.";
+        description = lib.mdDoc "Number of history message sent to participants by default.";
       };
       roomDefaultLanguage = mkOption {
         type = types.str;
         default = "en";
-        description = "Default room language.";
+        description = lib.mdDoc "Default room language.";
       };
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional MUC specific configuration";
+        description = lib.mdDoc "Additional MUC specific configuration";
       };
     };
   };
@@ -394,30 +394,30 @@ let
     options = {
       domain = mkOption {
         type = types.nullOr types.str;
-        description = "Domain name for the http-upload service";
+        description = lib.mdDoc "Domain name for the http-upload service";
       };
       uploadFileSizeLimit = mkOption {
         type = types.str;
         default = "50 * 1024 * 1024";
-        description = "Maximum file size, in bytes. Defaults to 50MB.";
+        description = lib.mdDoc "Maximum file size, in bytes. Defaults to 50MB.";
       };
       uploadExpireAfter = mkOption {
         type = types.str;
         default = "60 * 60 * 24 * 7";
-        description = "Max age of a file before it gets deleted, in seconds.";
+        description = lib.mdDoc "Max age of a file before it gets deleted, in seconds.";
       };
       userQuota = mkOption {
         type = types.nullOr types.int;
         default = null;
         example = 1234;
-        description = ''
+        description = lib.mdDoc ''
           Maximum size of all uploaded files per user, in bytes. There
           will be no quota if this option is set to null.
         '';
       };
       httpUploadPath = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Directory where the uploaded files will be stored. By
           default, uploaded files are put in a sub-directory of the
           default Prosody storage path (usually /var/lib/prosody).
@@ -434,25 +434,25 @@ let
       # TODO: require attribute
       domain = mkOption {
         type = types.str;
-        description = "Domain name";
+        description = lib.mdDoc "Domain name";
       };
 
       enabled = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the virtual host";
+        description = lib.mdDoc "Whether to enable the virtual host";
       };
 
       ssl = mkOption {
         type = types.nullOr (types.submodule sslOpts);
         default = null;
-        description = "Paths to SSL files";
+        description = lib.mdDoc "Paths to SSL files";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional virtual host specific configuration";
+        description = lib.mdDoc "Additional virtual host specific configuration";
       };
 
     };
@@ -472,13 +472,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the prosody server";
+        description = lib.mdDoc "Whether to enable the prosody server";
       };
 
       xmppComplianceSuite = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           The XEP-0423 defines a set of recommended XEPs to implement
           for a server. It's generally a good idea to implement this
           set of extensions if you want to provide your users with a
@@ -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
@@ -498,7 +498,7 @@ in
 
       package = mkOption {
         type = types.package;
-        description = "Prosody package to use";
+        description = lib.mdDoc "Prosody package to use";
         default = pkgs.prosody;
         defaultText = literalExpression "pkgs.prosody";
         example = literalExpression ''
@@ -511,63 +511,84 @@ in
 
       dataDir = mkOption {
         type = types.path;
-        description = "Directory where Prosody stores its data";
         default = "/var/lib/prosody";
+        description = lib.mdDoc ''
+          The prosody home directory used to store all data. If left as the default value
+          this directory will automatically be created before the prosody server starts, otherwise
+          you are responsible for ensuring the directory exists with appropriate ownership
+          and permissions.
+        '';
       };
 
       disco_items = mkOption {
         type = types.listOf (types.submodule discoOpts);
         default = [];
-        description = "List of discoverable items you want to advertise.";
+        description = lib.mdDoc "List of discoverable items you want to advertise.";
       };
 
       user = mkOption {
         type = types.str;
         default = "prosody";
-        description = "User account under which prosody runs.";
+        description = lib.mdDoc ''
+          User account under which prosody 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 prosody service starts.
+          :::
+        '';
       };
 
       group = mkOption {
         type = types.str;
         default = "prosody";
-        description = "Group account under which prosody runs.";
+        description = lib.mdDoc ''
+          Group account under which prosody 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 prosody service starts.
+          :::
+        '';
       };
 
       allowRegistration = mkOption {
         type = types.bool;
         default = false;
-        description = "Allow account creation";
+        description = lib.mdDoc "Allow account creation";
       };
 
       # HTTP server-related options
       httpPorts = mkOption {
         type = types.listOf types.int;
-        description = "Listening HTTP ports list for this service.";
+        description = lib.mdDoc "Listening HTTP ports list for this service.";
         default = [ 5280 ];
       };
 
       httpInterfaces = mkOption {
         type = types.listOf types.str;
         default = [ "*" "::" ];
-        description = "Interfaces on which the HTTP server will listen on.";
+        description = lib.mdDoc "Interfaces on which the HTTP server will listen on.";
       };
 
       httpsPorts = mkOption {
         type = types.listOf types.int;
-        description = "Listening HTTPS ports list for this service.";
+        description = lib.mdDoc "Listening HTTPS ports list for this service.";
         default = [ 5281 ];
       };
 
       httpsInterfaces = mkOption {
         type = types.listOf types.str;
         default = [ "*" "::" ];
-        description = "Interfaces on which the HTTPS server will listen on.";
+        description = lib.mdDoc "Interfaces on which the HTTPS server will listen on.";
       };
 
       c2sRequireEncryption = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Force clients to use encrypted connections? This option will
           prevent clients from authenticating unless they are using encryption.
         '';
@@ -576,7 +597,7 @@ in
       s2sRequireEncryption = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Force servers to use encrypted connections? This option will
           prevent servers from authenticating unless they are using encryption.
           Note that this is different from authentication.
@@ -586,7 +607,7 @@ in
       s2sSecureAuth = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Force certificate authentication for server-to-server connections?
           This provides ideal security, but requires servers you communicate
           with to support encryption AND present valid, trusted certificates.
@@ -598,7 +619,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "insecure.example.com" ];
-        description = ''
+        description = lib.mdDoc ''
           Some servers have invalid or self-signed certificates. You can list
           remote domains here that will not be required to authenticate using
           certificates. They will be authenticated using DNS instead, even
@@ -610,7 +631,7 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "jabber.org" ];
-        description = ''
+        description = lib.mdDoc ''
           Even if you leave s2s_secure_auth disabled, you can still require valid
           certificates for some domains by specifying a list here.
         '';
@@ -622,17 +643,17 @@ in
       extraModules = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Enable custom modules";
+        description = lib.mdDoc "Enable custom modules";
       };
 
       extraPluginPaths = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = "Addtional path in which to look find plugins/modules";
+        description = lib.mdDoc "Additional path in which to look find plugins/modules";
       };
 
       uploadHttp = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Configures the Prosody builtin HTTP server to handle user uploads.
         '';
         type = types.nullOr (types.submodule uploadHttpOpts);
@@ -648,12 +669,12 @@ in
         example = [ {
           domain = "conference.my-xmpp-example-host.org";
         } ];
-        description = "Multi User Chat (MUC) configuration";
+        description = lib.mdDoc "Multi User Chat (MUC) configuration";
       };
 
       virtualHosts = mkOption {
 
-        description = "Define the virtual hosts";
+        description = lib.mdDoc "Define the virtual hosts";
 
         type = with types; attrsOf (submodule vHostOpts);
 
@@ -676,27 +697,27 @@ in
       ssl = mkOption {
         type = types.nullOr (types.submodule sslOpts);
         default = null;
-        description = "Paths to SSL files";
+        description = lib.mdDoc "Paths to SSL files";
       };
 
       admins = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "admin1@example.com" "admin2@example.com" ];
-        description = "List of administrators of the current host";
+        description = lib.mdDoc "List of administrators of the current host";
       };
 
       authentication = mkOption {
         type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ];
         default = "internal_hashed";
         example = "internal_plain";
-        description = "Authentication mechanism used for logins.";
+        description = lib.mdDoc "Authentication mechanism used for logins.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional prosody configuration";
+        description = lib.mdDoc "Additional prosody configuration";
       };
 
     };
@@ -712,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.
@@ -820,6 +841,7 @@ in
         '') cfg.muc}
 
       ${ lib.optionalString (cfg.uploadHttp != null) ''
+        -- TODO: think about migrating this to mod-http_file_share instead.
         Component ${toLua cfg.uploadHttp.domain} "http_upload"
             http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
             http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
@@ -838,9 +860,8 @@ in
     users.users.prosody = mkIf (cfg.user == "prosody") {
       uid = config.ids.uids.prosody;
       description = "Prosody user";
-      createHome = true;
       inherit (cfg) group;
-      home = "${cfg.dataDir}";
+      home = cfg.dataDir;
     };
 
     users.groups.prosody = mkIf (cfg.group == "prosody") {
@@ -853,28 +874,33 @@ in
       wants = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ];
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        Type = "forking";
-        RuntimeDirectory = [ "prosody" ];
-        PIDFile = "/run/prosody/prosody.pid";
-        ExecStart = "${cfg.package}/bin/prosodyctl start";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-
-        MemoryDenyWriteExecute = true;
-        PrivateDevices = true;
-        PrivateMounts = true;
-        PrivateTmp = true;
-        ProtectControlGroups = true;
-        ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelModules = true;
-        ProtectKernelTunables = true;
-        RestrictNamespaces = true;
-        RestrictRealtime = true;
-        RestrictSUIDSGID = true;
-      };
+      serviceConfig = mkMerge [
+        {
+          User = cfg.user;
+          Group = cfg.group;
+          Type = "forking";
+          RuntimeDirectory = [ "prosody" ];
+          PIDFile = "/run/prosody/prosody.pid";
+          ExecStart = "${cfg.package}/bin/prosodyctl start";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+          MemoryDenyWriteExecute = true;
+          PrivateDevices = true;
+          PrivateMounts = true;
+          PrivateTmp = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+        }
+        (mkIf (cfg.dataDir == "/var/lib/prosody") {
+          StateDirectory = "prosody";
+        })
+      ];
     };
 
   };
diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix
index 844c9a6b8b35..a074023b5ee4 100644
--- a/nixos/modules/services/networking/quassel.nix
+++ b/nixos/modules/services/networking/quassel.nix
@@ -17,12 +17,12 @@ 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;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to the certificate used for SSL connections with clients.
         '';
       };
@@ -30,7 +30,7 @@ in
       requireSSL = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Require SSL for connections from clients.
         '';
       };
@@ -39,7 +39,7 @@ in
         type = types.package;
         default = pkgs.quasselDaemon;
         defaultText = literalExpression "pkgs.quasselDaemon";
-        description = ''
+        description = lib.mdDoc ''
           The package of the quassel daemon.
         '';
       };
@@ -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.
         '';
       };
@@ -57,7 +57,7 @@ in
       portNumber = mkOption {
         type = types.port;
         default = 4242;
-        description = ''
+        description = lib.mdDoc ''
           The port number the Quassel daemon will be listening to.
         '';
       };
@@ -68,7 +68,7 @@ in
           "/home/''${config.${opt.user}}/.config/quassel-irc.org"
         '';
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The directory holding configuration files, the SQlite database and the SSL Cert.
         '';
       };
@@ -76,7 +76,7 @@ in
       user = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           The existing user the Quassel daemon should run as. If left empty, a default "quassel" user will be created.
         '';
       };
diff --git a/nixos/modules/services/networking/quicktun.nix b/nixos/modules/services/networking/quicktun.nix
index 438e67d5ebb6..7aed972adc88 100644
--- a/nixos/modules/services/networking/quicktun.nix
+++ b/nixos/modules/services/networking/quicktun.nix
@@ -13,72 +13,72 @@ with lib;
 
     services.quicktun = mkOption {
       default = { };
-      description = "QuickTun tunnels";
+      description = lib.mdDoc "QuickTun tunnels";
       type = types.attrsOf (types.submodule {
         options = {
           tunMode = mkOption {
             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/nixos/modules/services/networking/quorum.nix b/nixos/modules/services/networking/quorum.nix
index bddcd18c7fbe..4b90b12f86fc 100644
--- a/nixos/modules/services/networking/quorum.nix
+++ b/nixos/modules/services/networking/quorum.nix
@@ -13,87 +13,87 @@ in {
   options = {
 
     services.quorum = {
-      enable = mkEnableOption "Quorum blockchain daemon";
+      enable = mkEnableOption (lib.mdDoc "Quorum blockchain daemon");
 
       user = mkOption {
         type = types.str;
         default = "quorum";
-        description = "The user as which to run quorum.";
+        description = lib.mdDoc "The user as which to run quorum.";
       };
 
       group = mkOption {
         type = types.str;
         default = cfg.user;
         defaultText = literalExpression "config.${opt.user}";
-        description = "The group as which to run quorum.";
+        description = lib.mdDoc "The group as which to run quorum.";
       };
 
       port = mkOption {
         type = types.port;
         default = 21000;
-        description = "Override the default port on which to listen for connections.";
+        description = lib.mdDoc "Override the default port on which to listen for connections.";
       };
 
       nodekeyFile = mkOption {
         type = types.path;
         default = "${dataDir}/nodekey";
-        description = "Path to the nodekey.";
+        description = lib.mdDoc "Path to the nodekey.";
       };
 
       staticNodes = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "enode://dd333ec28f0a8910c92eb4d336461eea1c20803eed9cf2c056557f986e720f8e693605bba2f4e8f289b1162e5ac7c80c914c7178130711e393ca76abc1d92f57@0.0.0.0:30303?discport=0" ];
-        description = "List of validator nodes.";
+        description = lib.mdDoc "List of validator nodes.";
       };
 
       privateconfig = mkOption {
         type = types.str;
         default = "ignore";
-        description = "Configuration of privacy transaction manager.";
+        description = lib.mdDoc "Configuration of privacy transaction manager.";
       };
 
       syncmode = mkOption {
         type = types.enum [ "fast" "full" "light" ];
         default = "full";
-        description = "Blockchain sync mode.";
+        description = lib.mdDoc "Blockchain sync mode.";
       };
 
       blockperiod = mkOption {
         type = types.int;
         default = 5;
-        description = "Default minimum difference between two consecutive block's timestamps in seconds.";
+        description = lib.mdDoc "Default minimum difference between two consecutive block's timestamps in seconds.";
       };
 
       permissioned = mkOption {
         type = types.bool;
         default = true;
-        description = "Allow only a defined list of nodes to connect.";
+        description = lib.mdDoc "Allow only a defined list of nodes to connect.";
       };
 
       rpc = {
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = "Enable RPC interface.";
+          description = lib.mdDoc "Enable RPC interface.";
         };
 
         address = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = "Listening address for RPC connections.";
+          description = lib.mdDoc "Listening address for RPC connections.";
         };
 
         port = mkOption {
           type = types.port;
           default = 22004;
-          description = "Override the default port on which to listen for RPC connections.";
+          description = lib.mdDoc "Override the default port on which to listen for RPC connections.";
         };
 
         api = mkOption {
           type = types.str;
           default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul";
-          description = "API's offered over the HTTP-RPC interface.";
+          description = lib.mdDoc "API's offered over the HTTP-RPC interface.";
         };
       };
 
@@ -101,31 +101,31 @@ in {
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = "Enable WS-RPC interface.";
+          description = lib.mdDoc "Enable WS-RPC interface.";
         };
 
         address = mkOption {
           type = types.str;
           default = "0.0.0.0";
-          description = "Listening address for WS-RPC connections.";
+          description = lib.mdDoc "Listening address for WS-RPC connections.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8546;
-          description = "Override the default port on which to listen for WS-RPC connections.";
+          description = lib.mdDoc "Override the default port on which to listen for WS-RPC connections.";
         };
 
         api = mkOption {
           type = types.str;
           default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul";
-          description = "API's offered over the WS-RPC interface.";
+          description = lib.mdDoc "API's offered over the WS-RPC interface.";
         };
 
        origins = mkOption {
           type = types.str;
           default = "*";
-          description = "Origins from which to accept websockets requests";
+          description = lib.mdDoc "Origins from which to accept websockets requests";
        };
      };
 
@@ -160,7 +160,7 @@ in {
           parentHash = "0x0000000000000000000000000000000000000000000000000000000000000000";
           timestamp = "0x00";
           }'';
-        description = "Blockchain genesis settings.";
+        description = lib.mdDoc "Blockchain genesis settings.";
       };
      };
   };
diff --git a/nixos/modules/services/networking/r53-ddns.nix b/nixos/modules/services/networking/r53-ddns.nix
new file mode 100644
index 000000000000..277b65dcecd4
--- /dev/null
+++ b/nixos/modules/services/networking/r53-ddns.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.r53-ddns;
+  pkg = pkgs.r53-ddns;
+in
+{
+  options = {
+    services.r53-ddns = {
+
+      enable = mkEnableOption (lib.mdDoc "r53-ddyns");
+
+      interval = mkOption {
+        type = types.str;
+        default = "15min";
+        description = lib.mdDoc "How often to update the entry";
+      };
+
+      zoneID = mkOption {
+        type = types.str;
+        description = lib.mdDoc "The ID of your zone in Route53";
+      };
+
+      domain = mkOption {
+        type = types.str;
+        description = lib.mdDoc "The name of your domain in Route53";
+      };
+
+      hostname = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Manually specify the hostname. Otherwise the tool will try to use the name
+          returned by the OS (Call to gethostname)
+        '';
+      };
+
+      environmentFile = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          File containing the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
+          in the format of an EnvironmentFile as described by systemd.exec(5)
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.timers.r53-ddns = {
+      description = "r53-ddns timer";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnBootSec = cfg.interval;
+        OnUnitActiveSec = cfg.interval;
+      };
+    };
+
+    systemd.services.r53-ddns = {
+      description = "r53-ddns service";
+      serviceConfig = {
+        ExecStart = "${pkg}/bin/r53-ddns -zone-id ${cfg.zoneID} -domain ${cfg.domain}"
+          + lib.optionalString (cfg.hostname != null) " -hostname ${cfg.hostname}";
+        EnvironmentFile = "${cfg.environmentFile}";
+        DynamicUser = true;
+      };
+    };
+
+  };
+}
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index c6c40777ed7c..9ec507fe2ab6 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -25,10 +25,10 @@ let
 
 in {
   options.services.radicale = {
-    enable = mkEnableOption "Radicale CalDAV and CardDAV server";
+    enable = mkEnableOption (lib.mdDoc "Radicale CalDAV and CardDAV server");
 
     package = mkOption {
-      description = "Radicale package to use.";
+      description = lib.mdDoc "Radicale package to use.";
       # Default cannot be pkgs.radicale because non-null values suppress
       # warnings about incompatible configuration and storage formats.
       type = with types; nullOr package // { inherit (package) description; };
@@ -39,21 +39,21 @@ in {
     config = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Radicale configuration, this will set the service
         configuration file.
-        This option is mutually exclusive with <option>settings</option>.
-        This option is deprecated.  Use <option>settings</option> instead.
+        This option is mutually exclusive with {option}`settings`.
+        This option is deprecated.  Use {option}`settings` instead.
       '';
     };
 
     settings = mkOption {
       type = format.type;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Configuration for Radicale. See
-        <link xlink:href="https://radicale.org/3.0.html#documentation/configuration" />.
-        This option is mutually exclusive with <option>config</option>.
+        <https://radicale.org/3.0.html#documentation/configuration>.
+        This option is mutually exclusive with {option}`config`.
       '';
       example = literalExpression ''
         server = {
@@ -72,12 +72,12 @@ in {
 
     rights = mkOption {
       type = format.type;
-      description = ''
+      description = lib.mdDoc ''
         Configuration for Radicale's rights file. See
-        <link xlink:href="https://radicale.org/3.0.html#documentation/authentication-and-rights" />.
-        This option only works in conjunction with <option>settings</option>.
-        Setting this will also set <option>settings.rights.type</option> and
-        <option>settings.rights.file</option> to approriate values.
+        <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 appropriate values.
       '';
       default = { };
       example = literalExpression ''
@@ -102,7 +102,7 @@ in {
     extraArgs = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = "Extra arguments passed to the Radicale daemon.";
+      description = lib.mdDoc "Extra arguments passed to the Radicale daemon.";
     };
   };
 
@@ -164,7 +164,7 @@ in {
         StateDirectoryMode = "0750";
         # Hardening
         CapabilityBoundingSet = [ "" ];
-        DeviceAllow = [ "/dev/stdin" ];
+        DeviceAllow = [ "/dev/stdin" "/dev/urandom" ];
         DevicePolicy = "strict";
         IPAddressAllow = mkIf bindLocalhost "localhost";
         IPAddressDeny = mkIf bindLocalhost "any";
diff --git a/nixos/modules/services/networking/radvd.nix b/nixos/modules/services/networking/radvd.nix
index 6e8db55bbf0d..72590eda4ee6 100644
--- a/nixos/modules/services/networking/radvd.nix
+++ b/nixos/modules/services/networking/radvd.nix
@@ -16,15 +16,15 @@ in
 
   ###### interface
 
-  options = {
+  options.services.radvd = {
 
-    services.radvd.enable = mkOption {
+    enable = mkOption {
       type = types.bool;
       default = false;
       description =
-        ''
+        lib.mdDoc ''
           Whether to enable the Router Advertisement Daemon
-          (<command>radvd</command>), which provides link-local
+          ({command}`radvd`), which provides link-local
           advertisements of IPv6 router addresses and prefixes using
           the Neighbor Discovery Protocol (NDP).  This enables
           stateless address autoconfiguration in IPv6 clients on the
@@ -32,7 +32,16 @@ in
         '';
     };
 
-    services.radvd.config = mkOption {
+    package = mkOption {
+      type = types.package;
+      default = pkgs.radvd;
+      defaultText = literalExpression "pkgs.radvd";
+      description = lib.mdDoc ''
+        The RADVD package to use for the RADVD service.
+      '';
+    };
+
+    config = mkOption {
       type = types.lines;
       example =
         ''
@@ -42,7 +51,7 @@ in
           };
         '';
       description =
-        ''
+        lib.mdDoc ''
           The contents of the radvd configuration file.
         '';
     };
@@ -67,7 +76,7 @@ in
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
         serviceConfig =
-          { ExecStart = "@${pkgs.radvd}/bin/radvd radvd -n -u radvd -C ${confFile}";
+          { ExecStart = "@${cfg.package}/bin/radvd radvd -n -u radvd -C ${confFile}";
             Restart = "always";
           };
       };
diff --git a/nixos/modules/services/networking/rdnssd.nix b/nixos/modules/services/networking/rdnssd.nix
index fd04bb8108f0..c63356e73468 100644
--- a/nixos/modules/services/networking/rdnssd.nix
+++ b/nixos/modules/services/networking/rdnssd.nix
@@ -21,10 +21,10 @@ in
       default = false;
       #default = config.networking.enableIPv6;
       description =
-        ''
+        lib.mdDoc ''
           Whether to enable the RDNSS daemon
-          (<command>rdnssd</command>), which configures DNS servers in
-          <filename>/etc/resolv.conf</filename> from RDNSS
+          ({command}`rdnssd`), which configures DNS servers in
+          {file}`/etc/resolv.conf` from RDNSS
           advertisements sent by IPv6 routers.
         '';
     };
diff --git a/nixos/modules/services/networking/redsocks.nix b/nixos/modules/services/networking/redsocks.nix
index 8481f9debf39..45feb1313c92 100644
--- a/nixos/modules/services/networking/redsocks.nix
+++ b/nixos/modules/services/networking/redsocks.nix
@@ -11,26 +11,26 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable redsocks.";
+        description = lib.mdDoc "Whether to enable redsocks.";
       };
 
       log_debug = mkOption {
         type = types.bool;
         default = false;
-        description = "Log connection progress.";
+        description = lib.mdDoc "Log connection progress.";
       };
 
       log_info = mkOption {
         type = types.bool;
         default = false;
-        description = "Log start and end of client sessions.";
+        description = lib.mdDoc "Log start and end of client sessions.";
       };
 
       log = mkOption {
         type = types.str;
         default = "stderr";
         description =
-          ''
+          lib.mdDoc ''
             Where to send logs.
 
             Possible values are:
@@ -45,7 +45,7 @@ in
         type = with types; nullOr str;
         default = null;
         description =
-          ''
+          lib.mdDoc ''
             Chroot under which to run redsocks. Log file is opened before
             chroot, but if logging to syslog /etc/localtime may be required.
           '';
@@ -53,7 +53,7 @@ in
 
       redsocks = mkOption {
         description =
-          ''
+          lib.mdDoc ''
             Local port to proxy associations to be performed.
 
             The example shows how to configure a proxy to handle port 80 as HTTP
@@ -74,22 +74,22 @@ in
             type = types.str;
             default = "127.0.0.1";
             description =
-              ''
+              lib.mdDoc ''
                 IP on which redsocks should listen. Defaults to 127.0.0.1 for
                 security reasons.
               '';
           };
 
           port = mkOption {
-            type = types.int;
+            type = types.port;
             default = 12345;
-            description = "Port on which redsocks should listen.";
+            description = lib.mdDoc "Port on which redsocks should listen.";
           };
 
           proxy = mkOption {
             type = types.str;
             description =
-              ''
+              lib.mdDoc ''
                 Proxy through which redsocks should forward incoming traffic.
                 Example: "example.org:8080"
               '';
@@ -97,20 +97,20 @@ in
 
           type = mkOption {
             type = types.enum [ "socks4" "socks5" "http-connect" "http-relay" ];
-            description = "Type of proxy.";
+            description = lib.mdDoc "Type of proxy.";
           };
 
           login = mkOption {
             type = with types; nullOr str;
             default = null;
-            description = "Login to send to proxy.";
+            description = lib.mdDoc "Login to send to proxy.";
           };
 
           password = mkOption {
             type = with types; nullOr str;
             default = null;
             description =
-              ''
+              lib.mdDoc ''
                 Password to send to proxy. WARNING, this will end up
                 world-readable in the store! Awaiting
                 https://github.com/NixOS/nix/issues/8 to be able to fix.
@@ -122,7 +122,7 @@ 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:
@@ -135,14 +135,14 @@ in
           redirectInternetOnly = mkOption {
             type = types.bool;
             default = true;
-            description = "Exclude all non-globally-routable IPs from redsocks";
+            description = lib.mdDoc "Exclude all non-globally-routable IPs from redsocks";
           };
 
           doNotRedirect = mkOption {
             type = with types; listOf str;
             default = [];
             description =
-              ''
+              lib.mdDoc ''
                 Iptables filters that if matched will get the packet off of
                 redsocks.
               '';
@@ -153,7 +153,7 @@ in
             type = with types; either bool str;
             default = false;
             description =
-              ''
+              lib.mdDoc ''
                 Conditions to make outbound packets go through this redsocks
                 instance.
 
diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix
index 891278506417..7f6358d00d0b 100644
--- a/nixos/modules/services/networking/resilio.nix
+++ b/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 = {
@@ -47,7 +76,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, start the Resilio Sync daemon. Once enabled, you can
           interact with the service through the Web UI, or configure it in your
           NixOS configuration.
@@ -59,7 +88,7 @@ in
         example = "Voltron";
         default = config.networking.hostName;
         defaultText = literalExpression "config.networking.hostName";
-        description = ''
+        description = lib.mdDoc ''
           Name of the Resilio Sync device.
         '';
       };
@@ -68,7 +97,7 @@ in
         type = types.int;
         default = 0;
         example = 44444;
-        description = ''
+        description = lib.mdDoc ''
           Listening port. Defaults to 0 which randomizes the port.
         '';
       };
@@ -76,7 +105,7 @@ in
       checkForUpdates = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Determines whether to check for updates and alert the user
           about them in the UI.
         '';
@@ -85,7 +114,7 @@ in
       useUpnp = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Use Universal Plug-n-Play (UPnP)
         '';
       };
@@ -94,7 +123,7 @@ in
         type = types.int;
         default = 0;
         example = 1024;
-        description = ''
+        description = lib.mdDoc ''
           Download speed limit. 0 is unlimited (default).
         '';
       };
@@ -103,7 +132,7 @@ in
         type = types.int;
         default = 0;
         example = 1024;
-        description = ''
+        description = lib.mdDoc ''
           Upload speed limit. 0 is unlimited (default).
         '';
       };
@@ -112,7 +141,7 @@ in
         type = types.str;
         default = "[::1]";
         example = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           HTTP address to bind to.
         '';
       };
@@ -120,7 +149,7 @@ in
       httpListenPort = mkOption {
         type = types.int;
         default = 9000;
-        description = ''
+        description = lib.mdDoc ''
           HTTP port to bind on.
         '';
       };
@@ -129,7 +158,7 @@ in
         type = types.str;
         example = "allyourbase";
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           HTTP web login username.
         '';
       };
@@ -138,7 +167,7 @@ in
         type = types.str;
         example = "arebelongtous";
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           HTTP web login password.
         '';
       };
@@ -146,23 +175,23 @@ in
       encryptLAN = mkOption {
         type = types.bool;
         default = true;
-        description = "Encrypt LAN data.";
+        description = lib.mdDoc "Encrypt LAN data.";
       };
 
       enableWebUI = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Web UI for administration. Bound to the specified
-          <literal>httpListenAddress</literal> and
-          <literal>httpListenPort</literal>.
+          `httpListenAddress` and
+          `httpListenPort`.
           '';
       };
 
       storagePath = mkOption {
         type = types.path;
         default = "/var/lib/resilio-sync/";
-        description = ''
+        description = lib.mdDoc ''
           Where BitTorrent Sync will store it's database files (containing
           things like username info and licenses). Generally, you should not
           need to ever change this.
@@ -172,21 +201,21 @@ in
       apiKey = mkOption {
         type = types.str;
         default = "";
-        description = "API key, which enables the developer API.";
+        description = lib.mdDoc "API key, which enables the developer API.";
       };
 
       directoryRoot = mkOption {
         type = types.str;
         default = "";
         example = "/media";
-        description = "Default directory to add folders in the web UI.";
+        description = lib.mdDoc "Default directory to add folders in the web UI.";
       };
 
       sharedFolders = mkOption {
         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/nixos/modules/services/networking/robustirc-bridge.nix b/nixos/modules/services/networking/robustirc-bridge.nix
index 255af79ec04b..9b93828c396c 100644
--- a/nixos/modules/services/networking/robustirc-bridge.nix
+++ b/nixos/modules/services/networking/robustirc-bridge.nix
@@ -8,12 +8,12 @@ in
 {
   options = {
     services.robustirc-bridge = {
-      enable = mkEnableOption "RobustIRC bridge";
+      enable = mkEnableOption (lib.mdDoc "RobustIRC bridge");
 
       extraFlags = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''Extra flags passed to the <command>robustirc-bridge</command> command. See <link xlink:href="https://robustirc.net/docs/adminguide.html#_bridge">RobustIRC Documentation</link> or robustirc-bridge(1) for details.'';
+        description = lib.mdDoc ''Extra flags passed to the {command}`robustirc-bridge` command. See [RobustIRC Documentation](https://robustirc.net/docs/adminguide.html#_bridge) or robustirc-bridge(1) for details.'';
         example = [
           "-network robustirc.net"
         ];
diff --git a/nixos/modules/services/networking/routedns.nix b/nixos/modules/services/networking/routedns.nix
new file mode 100644
index 000000000000..2a29a06700ce
--- /dev/null
+++ b/nixos/modules/services/networking/routedns.nix
@@ -0,0 +1,84 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+with lib;
+
+let
+  cfg = config.services.routedns;
+  settingsFormat = pkgs.formats.toml { };
+in
+{
+  options.services.routedns = {
+    enable = mkEnableOption (lib.mdDoc "RouteDNS - DNS stub resolver, proxy and router");
+
+    settings = mkOption {
+      type = settingsFormat.type;
+      example = literalExpression ''
+        {
+          resolvers.cloudflare-dot = {
+            address = "1.1.1.1:853";
+            protocol = "dot";
+          };
+          groups.cloudflare-cached = {
+            type = "cache";
+            resolvers = ["cloudflare-dot"];
+          };
+          listeners.local-udp = {
+            address = "127.0.0.1:53";
+            protocol = "udp";
+            resolver = "cloudflare-cached";
+          };
+          listeners.local-tcp = {
+            address = "127.0.0.1:53";
+            protocol = "tcp";
+            resolver = "cloudflare-cached";
+          };
+        }
+      '';
+      description = lib.mdDoc ''
+        Configuration for RouteDNS, see <https://github.com/folbricht/routedns/blob/master/doc/configuration.md>
+        for more information.
+      '';
+    };
+
+    configFile = mkOption {
+      default = settingsFormat.generate "routedns.toml" cfg.settings;
+      defaultText = "A RouteDNS configuration file automatically generated by values from services.routedns.*";
+      type = types.path;
+      example = literalExpression ''"''${pkgs.routedns}/cmd/routedns/example-config/use-case-1.toml"'';
+      description = lib.mdDoc "Path to RouteDNS TOML configuration file.";
+    };
+
+    package = mkOption {
+      default = pkgs.routedns;
+      defaultText = literalExpression "pkgs.routedns";
+      type = types.package;
+      description = lib.mdDoc "RouteDNS package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.routedns = {
+      description = "RouteDNS - DNS stub resolver, proxy and router";
+      after = [ "network.target" ]; # in case a bootstrap resolver is used, this might fail a few times until the respective server is actually reachable
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+      startLimitIntervalSec = 30;
+      startLimitBurst = 5;
+      serviceConfig = {
+        Restart = "on-failure";
+        RestartSec = "5s";
+        LimitNPROC = 512;
+        LimitNOFILE = 1048576;
+        DynamicUser = true;
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        NoNewPrivileges = true;
+        ExecStart = "${getBin cfg.package}/bin/routedns -l 4 ${cfg.configFile}";
+      };
+    };
+  };
+  meta.maintainers = with maintainers; [ jsimonetti ];
+}
diff --git a/nixos/modules/services/networking/rpcbind.nix b/nixos/modules/services/networking/rpcbind.nix
index 0a5df6987092..60e78dfec51b 100644
--- a/nixos/modules/services/networking/rpcbind.nix
+++ b/nixos/modules/services/networking/rpcbind.nix
@@ -13,7 +13,7 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        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
@@ -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/nixos/modules/services/networking/rxe.nix b/nixos/modules/services/networking/rxe.nix
index 868e2c81ccbd..7dbb4823b4bc 100644
--- a/nixos/modules/services/networking/rxe.nix
+++ b/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/nixos/modules/services/networking/sabnzbd.nix b/nixos/modules/services/networking/sabnzbd.nix
index 54eeba1a9ec1..8f3545df8995 100644
--- a/nixos/modules/services/networking/sabnzbd.nix
+++ b/nixos/modules/services/networking/sabnzbd.nix
@@ -15,31 +15,31 @@ 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";
-        description = "The sabnzbd executable package run by the service.";
+        defaultText = lib.literalExpression "pkgs.sabnzbd";
+        description = lib.mdDoc "The sabnzbd executable package run by the service.";
       };
 
       configFile = mkOption {
         type = types.path;
         default = "/var/lib/sabnzbd/sabnzbd.ini";
-        description = "Path to config file.";
+        description = lib.mdDoc "Path to config file.";
       };
 
       user = mkOption {
         default = "sabnzbd";
         type = types.str;
-        description = "User to run the service as";
+        description = lib.mdDoc "User to run the service as";
       };
 
       group = mkOption {
         type = types.str;
         default = "sabnzbd";
-        description = "Group to run the service as";
+        description = lib.mdDoc "Group to run the service as";
       };
     };
   };
diff --git a/nixos/modules/services/networking/seafile.nix b/nixos/modules/services/networking/seafile.nix
index 2839ffb60a1f..b07d51b9b49a 100644
--- a/nixos/modules/services/networking/seafile.nix
+++ b/nixos/modules/services/networking/seafile.nix
@@ -19,6 +19,8 @@ let
     MEDIA_ROOT = '${seahubDir}/media/'
     THUMBNAIL_ROOT = '${seahubDir}/thumbnail/'
 
+    SERVICE_URL = '${cfg.ccnetSettings.General.SERVICE_URL}'
+
     with open('${seafRoot}/.seahubSecret') as f:
         SECRET_KEY = f.readline().rstrip()
 
@@ -35,7 +37,7 @@ in {
   ###### Interface
 
   options.services.seafile = {
-    enable = mkEnableOption "Seafile server";
+    enable = mkEnableOption (lib.mdDoc "Seafile server");
 
     ccnetSettings = mkOption {
       type = types.submodule {
@@ -46,7 +48,7 @@ in {
             SERVICE_URL = mkOption {
               type = types.str;
               example = "https://www.example.com";
-              description = ''
+              description = lib.mdDoc ''
                 Seahub public URL.
               '';
             };
@@ -54,9 +56,9 @@ in {
         };
       };
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Configuration for ccnet, see
-        <link xlink:href="https://manual.seafile.com/config/ccnet-conf/"/>
+        <https://manual.seafile.com/config/ccnet-conf/>
         for supported values.
       '';
     };
@@ -70,7 +72,7 @@ in {
             port = mkOption {
               type = types.port;
               default = 8082;
-              description = ''
+              description = lib.mdDoc ''
                 The tcp port used by seafile fileserver.
               '';
             };
@@ -78,7 +80,7 @@ in {
               type = types.str;
               default = "127.0.0.1";
               example = "0.0.0.0";
-              description = ''
+              description = lib.mdDoc ''
                 The binding address used by seafile fileserver.
               '';
             };
@@ -86,9 +88,9 @@ in {
         };
       };
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Configuration for seafile-server, see
-        <link xlink:href="https://manual.seafile.com/config/seafile-conf/"/>
+        <https://manual.seafile.com/config/seafile-conf/>
         for supported values.
       '';
     };
@@ -97,7 +99,7 @@ in {
       type = types.int;
       default = 4;
       example = 10;
-      description = ''
+      description = lib.mdDoc ''
         The number of gunicorn worker processes for handling requests.
       '';
     };
@@ -105,7 +107,7 @@ in {
     adminEmail = mkOption {
       example = "john@example.com";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Seafile Seahub Admin Account Email.
       '';
     };
@@ -113,7 +115,7 @@ in {
     initialAdminPassword = mkOption {
       example = "someStrongPass";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Seafile Seahub Admin Account initial password.
         Should be change via Seahub web front-end.
       '';
@@ -121,7 +123,7 @@ in {
 
     seafilePackage = mkOption {
       type = types.package;
-      description = "Which package to use for the seafile server.";
+      description = lib.mdDoc "Which package to use for the seafile server.";
       default = pkgs.seafile-server;
       defaultText = literalExpression "pkgs.seafile-server";
     };
@@ -129,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.
       '';
     };
@@ -177,6 +179,7 @@ in {
         after = [ "network.target" ];
         wantedBy = [ "seafile.target" ];
         restartTriggers = [ ccnetConf seafileConf ];
+        path = [ pkgs.sqlite ];
         serviceConfig = securityOptions // {
           User = "seafile";
           Group = "seafile";
@@ -200,11 +203,11 @@ in {
           if [ ! -f "${seafRoot}/server-setup" ]; then
               mkdir -p ${dataDir}/library-template
               mkdir -p ${ccnetDir}/{GroupMgr,misc,OrgMgr,PeerMgr}
-              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/GroupMgr/groupmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/groupmgr.sql"
-              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/misc/config.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/config.sql"
-              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/OrgMgr/orgmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/org.sql"
-              ${pkgs.sqlite}/bin/sqlite3 ${ccnetDir}/PeerMgr/usermgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/user.sql"
-              ${pkgs.sqlite}/bin/sqlite3 ${dataDir}/seafile.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/seafile.sql"
+              sqlite3 ${ccnetDir}/GroupMgr/groupmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/groupmgr.sql"
+              sqlite3 ${ccnetDir}/misc/config.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/config.sql"
+              sqlite3 ${ccnetDir}/OrgMgr/orgmgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/org.sql"
+              sqlite3 ${ccnetDir}/PeerMgr/usermgr.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/user.sql"
+              sqlite3 ${dataDir}/seafile.db ".read ${cfg.seafilePackage}/share/seafile/sql/sqlite/seafile.sql"
               echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup
           fi
           # checking for upgrades and handling them
@@ -213,7 +216,14 @@ in {
           installedMinor=$(cat "${seafRoot}/server-setup" | cut -d"-" -f1 | cut -d"." -f2)
           pkgMajor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f1)
           pkgMinor=$(echo "${cfg.seafilePackage.version}" | cut -d"." -f2)
-          if [ $installedMajor != $pkgMajor ] || [ $installedMinor != $pkgMinor ]; then
+
+          if [[ $installedMajor == $pkgMajor && $installedMinor == $pkgMinor ]]; then
+             :
+          elif [[ $installedMajor == 8 && $installedMinor == 0 && $pkgMajor == 9 && $pkgMinor == 0 ]]; then
+              # Upgrade from 8.0 to 9.0
+              sqlite3 ${dataDir}/seafile.db ".read ${pkgs.seahub}/scripts/upgrade/sql/9.0.0/sqlite3/seafile.sql"
+              echo "${cfg.seafilePackage.version}-sqlite" > "${seafRoot}"/server-setup
+          else
               echo "Unsupported upgrade" >&2
               exit 1
           fi
diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix
index b73f255eb9dd..6c57ddbde2d4 100644
--- a/nixos/modules/services/networking/searx.nix
+++ b/nixos/modules/services/networking/searx.nix
@@ -51,14 +51,14 @@ in
         type = types.bool;
         default = false;
         relatedPackages = [ "searx" ];
-        description = "Whether to enable Searx, the meta search engine.";
+        description = lib.mdDoc "Whether to enable Searx, the meta search engine.";
       };
 
       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
           Searx. This option can be used to safely include secret keys into the
           Searx configuration.
@@ -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.
+          :::
         '';
       };
 
@@ -117,21 +115,20 @@ in
         type = types.package;
         default = pkgs.searx;
         defaultText = literalExpression "pkgs.searx";
-        description = "searx package to use.";
+        description = lib.mdDoc "searx package to use.";
       };
 
       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.
+          :::
         '';
       };
 
@@ -143,9 +140,10 @@ in
             disable-logging = true;
             http = ":8080";                   # serve via HTTP...
             socket = "/run/searx/searx.sock"; # ...or UNIX socket
+            chmod-socket = "660";             # allow the searx group to read/write to the socket
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Additional configuration of the uWSGI vassal running searx. It
           should notably specify on which interfaces and ports the vassal
           should listen.
@@ -194,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)
@@ -220,7 +221,12 @@ in
         lazy-apps = true;
         enable-threads = true;
         module = "searx.webapp";
-        env = [ "SEARX_SETTINGS_PATH=${cfg.settingsFile}" ];
+        env = [
+          "SEARX_SETTINGS_PATH=${cfg.settingsFile}"
+          # searxng compatibility https://github.com/searxng/searxng/issues/1519
+          "SEARXNG_SETTINGS_PATH=${cfg.settingsFile}"
+        ];
+        buffer-size = 32768;
         pythonPackages = self: [ cfg.package ];
       } // cfg.uwsgiConfig;
     };
diff --git a/nixos/modules/services/networking/shadowsocks.nix b/nixos/modules/services/networking/shadowsocks.nix
index 7bea269a9ed0..2034dca6f26b 100644
--- a/nixos/modules/services/networking/shadowsocks.nix
+++ b/nixos/modules/services/networking/shadowsocks.nix
@@ -34,7 +34,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run shadowsocks-libev shadowsocks server.
         '';
       };
@@ -42,15 +42,15 @@ in
       localAddress = mkOption {
         type = types.coercedTo types.str singleton (types.listOf types.str);
         default = [ "[::0]" "0.0.0.0" ];
-        description = ''
+        description = lib.mdDoc ''
           Local addresses to which the server binds.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8388;
-        description = ''
+        description = lib.mdDoc ''
           Port which the server uses.
         '';
       };
@@ -58,7 +58,7 @@ in
       password = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Password for connecting clients.
         '';
       };
@@ -66,7 +66,7 @@ in
       passwordFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Password file with a password for connecting clients.
         '';
       };
@@ -74,7 +74,7 @@ in
       mode = mkOption {
         type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
         default = "tcp_and_udp";
-        description = ''
+        description = lib.mdDoc ''
           Relay protocols.
         '';
       };
@@ -82,7 +82,7 @@ in
       fastOpen = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           use TCP fast-open
         '';
       };
@@ -90,8 +90,8 @@ in
       encryptionMethod = mkOption {
         type = types.str;
         default = "chacha20-ietf-poly1305";
-        description = ''
-          Encryption method. See <link xlink:href="https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers"/>.
+        description = lib.mdDoc ''
+          Encryption method. See <https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers>.
         '';
       };
 
@@ -99,7 +99,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = literalExpression ''"''${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin"'';
-        description = ''
+        description = lib.mdDoc ''
           SIP003 plugin for shadowsocks
         '';
       };
@@ -108,7 +108,7 @@ in
         type = types.str;
         default = "";
         example = "server;host=example.com";
-        description = ''
+        description = lib.mdDoc ''
           Options to pass to the plugin if one was specified
         '';
       };
@@ -119,13 +119,13 @@ in
         example = {
           nameserver = "8.8.8.8";
         };
-        description = ''
+        description = lib.mdDoc ''
           Additional configuration for shadowsocks that is not covered by the
           provided options. The provided attrset will be serialized to JSON and
           has to contain valid shadowsocks options. Unfortunately most
           additional options are undocumented but it's easy to find out what is
           available by looking into the source code of
-          <link xlink:href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/src/jconf.c"/>
+          <https://github.com/shadowsocks/shadowsocks-libev/blob/master/src/jconf.c>
         '';
       };
     };
diff --git a/nixos/modules/services/networking/shairport-sync.nix b/nixos/modules/services/networking/shairport-sync.nix
index eb61663e4d92..75684eea3ad1 100644
--- a/nixos/modules/services/networking/shairport-sync.nix
+++ b/nixos/modules/services/networking/shairport-sync.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the shairport-sync daemon.
 
           Running with a local system-wide or remote pulseaudio server
@@ -30,7 +30,7 @@ in
       arguments = mkOption {
         type = types.str;
         default = "-v -o pa";
-        description = ''
+        description = lib.mdDoc ''
           Arguments to pass to the daemon. Defaults to a local pulseaudio
           server.
         '';
@@ -39,7 +39,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to automatically open ports in the firewall.
         '';
       };
@@ -47,7 +47,7 @@ in
       user = mkOption {
         type = types.str;
         default = "shairport";
-        description = ''
+        description = lib.mdDoc ''
           User account name under which to run shairport-sync. The account
           will be created.
         '';
@@ -56,7 +56,7 @@ in
       group = mkOption {
         type = types.str;
         default = "shairport";
-        description = ''
+        description = lib.mdDoc ''
           Group account name under which to run shairport-sync. The account
           will be created.
         '';
diff --git a/nixos/modules/services/networking/shellhub-agent.nix b/nixos/modules/services/networking/shellhub-agent.nix
index 57825945d9f7..ad33c50f9d63 100644
--- a/nixos/modules/services/networking/shellhub-agent.nix
+++ b/nixos/modules/services/networking/shellhub-agent.nix
@@ -12,14 +12,14 @@ in
 
     services.shellhub-agent = {
 
-      enable = mkEnableOption "ShellHub Agent daemon";
+      enable = mkEnableOption (lib.mdDoc "ShellHub Agent daemon");
 
       package = mkPackageOption pkgs "shellhub-agent" { };
 
       preferredHostname = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Set the device preferred hostname. This provides a hint to
           the server to use this as hostname if it is available.
         '';
@@ -28,7 +28,7 @@ in
       keepAliveInterval = mkOption {
         type = types.int;
         default = 30;
-        description = ''
+        description = lib.mdDoc ''
           Determine the interval to send the keep alive message to
           the server. This has a direct impact of the bandwidth
           used by the device.
@@ -38,7 +38,7 @@ in
       tenantId = mkOption {
         type = types.str;
         example = "ba0a880c-2ada-11eb-a35e-17266ef329d6";
-        description = ''
+        description = lib.mdDoc ''
           The tenant ID to use when connecting to the ShellHub
           Gateway.
         '';
@@ -47,7 +47,7 @@ in
       server = mkOption {
         type = types.str;
         default = "https://cloud.shellhub.io";
-        description = ''
+        description = lib.mdDoc ''
           Server address of ShellHub Gateway to connect.
         '';
       };
@@ -55,7 +55,7 @@ in
       privateKey = mkOption {
         type = types.path;
         default = "/var/lib/shellhub-agent/private.key";
-        description = ''
+        description = lib.mdDoc ''
           Location where to store the ShellHub Agent private
           key.
         '';
diff --git a/nixos/modules/services/networking/shorewall.nix b/nixos/modules/services/networking/shorewall.nix
index ac732d4b12e4..ba59d71120da 100644
--- a/nixos/modules/services/networking/shorewall.nix
+++ b/nixos/modules/services/networking/shorewall.nix
@@ -8,27 +8,26 @@ 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 {
         type        = types.package;
         default     = pkgs.shorewall;
         defaultText = lib.literalExpression "pkgs.shorewall";
-        description = "The shorewall package to use.";
+        description = lib.mdDoc "The shorewall package to use.";
       };
       configs = lib.mkOption {
         type        = types.attrsOf types.lines;
         default     = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines the Shorewall configs.
           The attribute name defines the name of the config,
           and the attribute value defines the content of the config.
diff --git a/nixos/modules/services/networking/shorewall6.nix b/nixos/modules/services/networking/shorewall6.nix
index 4235c74a3f80..e54be290bfb3 100644
--- a/nixos/modules/services/networking/shorewall6.nix
+++ b/nixos/modules/services/networking/shorewall6.nix
@@ -8,27 +8,26 @@ 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 {
         type        = types.package;
         default     = pkgs.shorewall;
         defaultText = lib.literalExpression "pkgs.shorewall";
-        description = "The shorewall package to use.";
+        description = lib.mdDoc "The shorewall package to use.";
       };
       configs = lib.mkOption {
         type        = types.attrsOf types.lines;
         default     = {};
-        description = ''
+        description = lib.mdDoc ''
           This option defines the Shorewall configs.
           The attribute name defines the name of the config,
           and the attribute value defines the content of the config.
diff --git a/nixos/modules/services/networking/shout.nix b/nixos/modules/services/networking/shout.nix
index cca03a8f88a1..0b1687d44d9e 100644
--- a/nixos/modules/services/networking/shout.nix
+++ b/nixos/modules/services/networking/shout.nix
@@ -23,37 +23,37 @@ let
 
 in {
   options.services.shout = {
-    enable = mkEnableOption "Shout web IRC client";
+    enable = mkEnableOption (lib.mdDoc "Shout web IRC client");
 
     private = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Make your shout instance private. You will need to configure user
-        accounts by adding entries in <filename>${shoutHome}/users</filename>.
+        accounts by adding entries in {file}`${shoutHome}/users`.
       '';
     };
 
     listenAddress = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = "IP interface to listen on for http connections.";
+      description = lib.mdDoc "IP interface to listen on for http connections.";
     };
 
     port = mkOption {
       type = types.port;
       default = 9000;
-      description = "TCP port to listen on for http connections.";
+      description = lib.mdDoc "TCP port to listen on for http connections.";
     };
 
     configFile = mkOption {
       type = types.nullOr types.lines;
       default = null;
-      description = ''
-        Contents of Shout's <filename>config.js</filename> file.
+      description = lib.mdDoc ''
+        Contents of Shout's {file}`config.js` file.
 
         Used for backward compatibility, recommended way is now to use
-        the <literal>config</literal> option.
+        the `config` option.
 
         Documentation: http://shout-irc.com/docs/server/configuration.html
       '';
@@ -70,8 +70,8 @@ in {
           port = 6697;
         };
       };
-      description = ''
-        Shout <filename>config.js</filename> contents as attribute set (will be
+      description = lib.mdDoc ''
+        Shout {file}`config.js` contents as attribute set (will be
         converted to JSON to generate the configuration file).
 
         The options defined here will be merged to the default configuration file.
diff --git a/nixos/modules/services/networking/skydns.nix b/nixos/modules/services/networking/skydns.nix
index dea60a3862a3..84cf6b0deac1 100644
--- a/nixos/modules/services/networking/skydns.nix
+++ b/nixos/modules/services/networking/skydns.nix
@@ -7,51 +7,51 @@ let
 
 in {
   options.services.skydns = {
-    enable = mkEnableOption "skydns service";
+    enable = mkEnableOption (lib.mdDoc "skydns service");
 
     etcd = {
       machines = mkOption {
         default = [ "http://127.0.0.1:2379" ];
         type = types.listOf types.str;
-        description = "Skydns list of etcd endpoints to connect to.";
+        description = lib.mdDoc "Skydns list of etcd endpoints to connect to.";
       };
 
       tlsKey = mkOption {
         default = null;
         type = types.nullOr types.path;
-        description = "Skydns path of TLS client certificate - private key.";
+        description = lib.mdDoc "Skydns path of TLS client certificate - private key.";
       };
 
       tlsPem = mkOption {
         default = null;
         type = types.nullOr types.path;
-        description = "Skydns path of TLS client certificate - public key.";
+        description = lib.mdDoc "Skydns path of TLS client certificate - public key.";
       };
 
       caCert = mkOption {
         default = null;
         type = types.nullOr types.path;
-        description = "Skydns path of TLS certificate authority public key.";
+        description = lib.mdDoc "Skydns path of TLS certificate authority public key.";
       };
     };
 
     address = mkOption {
       default = "0.0.0.0:53";
       type = types.str;
-      description = "Skydns address to bind to.";
+      description = lib.mdDoc "Skydns address to bind to.";
     };
 
     domain = mkOption {
       default = "skydns.local.";
       type = types.str;
-      description = "Skydns default domain if not specified by etcd config.";
+      description = lib.mdDoc "Skydns default domain if not specified by etcd config.";
     };
 
     nameservers = mkOption {
       default = map (n: n + ":53") config.networking.nameservers;
       defaultText = literalExpression ''map (n: n + ":53") config.networking.nameservers'';
       type = types.listOf types.str;
-      description = "Skydns list of nameservers to forward DNS requests to when not authoritative for a domain.";
+      description = lib.mdDoc "Skydns list of nameservers to forward DNS requests to when not authoritative for a domain.";
       example = ["8.8.8.8:53" "8.8.4.4:53"];
     };
 
@@ -59,13 +59,13 @@ in {
       default = pkgs.skydns;
       defaultText = literalExpression "pkgs.skydns";
       type = types.package;
-      description = "Skydns package to use.";
+      description = lib.mdDoc "Skydns package to use.";
     };
 
     extraConfig = mkOption {
       default = {};
       type = types.attrsOf types.str;
-      description = "Skydns attribute set of extra config options passed as environment variables.";
+      description = lib.mdDoc "Skydns attribute set of extra config options passed as environment variables.";
     };
   };
 
diff --git a/nixos/modules/services/networking/smartdns.nix b/nixos/modules/services/networking/smartdns.nix
index 7f9df42ce9c1..af8ee8b00c0a 100644
--- a/nixos/modules/services/networking/smartdns.nix
+++ b/nixos/modules/services/networking/smartdns.nix
@@ -20,12 +20,12 @@ let
     } cfg.settings);
 in {
   options.services.smartdns = {
-    enable = mkEnableOption "SmartDNS DNS server";
+    enable = mkEnableOption (lib.mdDoc "SmartDNS DNS server");
 
     bindPort = mkOption {
       type = types.port;
       default = 53;
-      description = "DNS listening port number.";
+      description = lib.mdDoc "DNS listening port number.";
     };
 
     settings = mkOption {
@@ -42,9 +42,9 @@ in {
           speed-check-mode = "ping,tcp:80";
         };
       '';
-      description = ''
-        A set that will be generated into configuration file, see the <link xlink:href="https://github.com/pymumu/smartdns/blob/master/ReadMe_en.md#configuration-parameter">SmartDNS README</link> for details of configuration parameters.
-        You could override the options here like <option>services.smartdns.bindPort</option> by writing <literal>settings.bind = ":5353 -no-rule -group example";</literal>.
+      description = lib.mdDoc ''
+        A set that will be generated into configuration file, see the [SmartDNS README](https://github.com/pymumu/smartdns/blob/master/ReadMe_en.md#configuration-parameter) for details of configuration parameters.
+        You could override the options here like {option}`services.smartdns.bindPort` by writing `settings.bind = ":5353 -no-rule -group example";`.
       '';
     };
   };
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index bd71b158dbe3..2e67f8b77c08 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -49,11 +49,8 @@ in
 {
   options = {
     services.smokeping = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Enable the smokeping service";
-      };
+      enable = mkEnableOption (lib.mdDoc "smokeping service");
+
       alertConfig = mkOption {
         type = types.lines;
         default = ''
@@ -70,20 +67,22 @@ in
           pattern = >0%,*12*,>0%,*12*,>0%
           comment = loss 3 times  in a row;
         '';
-        description = "Configuration for alerts.";
+        description = lib.mdDoc "Configuration for alerts.";
       };
       cgiUrl = mkOption {
         type = types.str;
         default = "http://${cfg.hostName}:${toString cfg.port}/smokeping.cgi";
         defaultText = literalExpression ''"http://''${hostName}:''${toString port}/smokeping.cgi"'';
         example = "https://somewhere.example.com/smokeping.cgi";
-        description = "URL to the smokeping cgi.";
+        description = lib.mdDoc "URL to the smokeping cgi.";
       };
       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;
@@ -113,28 +112,28 @@ in
               MAX  0.5 144   7200
               MIN  0.5 144   7200
         '';
-        description = ''Configure the ping frequency and retention of the rrd files.
+        description = lib.mdDoc ''Configure the ping frequency and retention of the rrd files.
           Once set, changing the interval will require deletion or migration of all
           the collected data.'';
       };
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Any additional customization not already included.";
+        description = lib.mdDoc "Any additional customization not already included.";
       };
       hostName = mkOption {
         type = types.str;
         default = config.networking.fqdn;
         defaultText = literalExpression "config.networking.fqdn";
         example = "somewhere.example.com";
-        description = "DNS name for the urls generated in the cgi.";
+        description = lib.mdDoc "DNS name for the urls generated in the cgi.";
       };
       imgUrl = mkOption {
         type = types.str;
         default = "cache";
         defaultText = literalExpression ''"cache"'';
         example = "https://somewhere.example.com/cache";
-        description = ''
+        description = lib.mdDoc ''
           Base url for images generated in the cgi.
 
           The default is a relative URL to ensure it works also when e.g. forwarding
@@ -145,48 +144,48 @@ in
         type = types.enum ["original" "absolute" "relative"];
         default = "relative";
         example = "absolute";
-        description = "DNS name for the urls generated in the cgi.";
+        description = lib.mdDoc "DNS name for the urls generated in the cgi.";
       };
       mailHost = mkOption {
         type = types.str;
         default = "";
         example = "localhost";
-        description = "Use this SMTP server to send alerts";
+        description = lib.mdDoc "Use this SMTP server to send alerts";
       };
       owner = mkOption {
         type = types.str;
         default = "nobody";
-        example = "Joe Admin";
-        description = "Real name of the owner of the instance";
+        example = "Bob Foobawr";
+        description = lib.mdDoc "Real name of the owner of the instance";
       };
       ownerEmail = mkOption {
         type = types.str;
         default = "no-reply@${cfg.hostName}";
         defaultText = literalExpression ''"no-reply@''${hostName}"'';
         example = "no-reply@yourdomain.com";
-        description = "Email contact for owner";
+        description = lib.mdDoc "Email contact for owner";
       };
       package = mkOption {
         type = types.package;
         default = pkgs.smokeping;
         defaultText = literalExpression "pkgs.smokeping";
-        description = "Specify a custom smokeping package";
+        description = lib.mdDoc "Specify a custom smokeping package";
       };
       host = mkOption {
         type = types.nullOr types.str;
         default = "localhost";
         example = "192.0.2.1"; # rfc5737 example IP for documentation
-        description = ''
+        description = lib.mdDoc ''
           Host/IP to bind to for the web server.
 
-          Setting it to <literal>null</literal> skips passing the -h option to thttpd,
+          Setting it to `null` skips passing the -h option to thttpd,
           which makes it bind to all interfaces.
         '';
       };
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8081;
-        description = "TCP port to use for the web server.";
+        description = lib.mdDoc "TCP port to use for the web server.";
       };
       presentationConfig = mkOption {
         type = types.lines;
@@ -227,13 +226,13 @@ in
           "Last 10 Days"    10d
           "Last 360 Days"   360d
         '';
-        description = "presentation graph style";
+        description = lib.mdDoc "presentation graph style";
       };
       presentationTemplate = mkOption {
         type = types.str;
         default = "${pkgs.smokeping}/etc/basepage.html.dist";
         defaultText = literalExpression ''"''${pkgs.smokeping}/etc/basepage.html.dist"'';
-        description = "Default page layout for the web UI.";
+        description = lib.mdDoc "Default page layout for the web UI.";
       };
       probeConfig = mkOption {
         type = types.lines;
@@ -247,19 +246,19 @@ in
             binary = ''${config.security.wrapperDir}/fping
           '''
         '';
-        description = "Probe configuration";
+        description = lib.mdDoc "Probe configuration";
       };
       sendmail = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/run/wrappers/bin/sendmail";
-        description = "Use this sendmail compatible script to deliver alerts";
+        description = lib.mdDoc "Use this sendmail compatible script to deliver alerts";
       };
       smokeMailTemplate = mkOption {
         type = types.str;
         default = "${cfg.package}/etc/smokemail.dist";
         defaultText = literalExpression ''"''${package}/etc/smokemail.dist"'';
-        description = "Specify the smokemail template for alerts.";
+        description = lib.mdDoc "Specify the smokemail template for alerts.";
       };
       targetConfig = mkOption {
         type = types.lines;
@@ -277,17 +276,17 @@ in
           title = This host
           host = localhost
         '';
-        description = "Target configuration";
+        description = lib.mdDoc "Target configuration";
       };
       user = mkOption {
         type = types.str;
         default = "smokeping";
-        description = "User that runs smokeping and (optionally) thttpd. A group of the same name will be created as well.";
+        description = lib.mdDoc "User that runs smokeping and (optionally) thttpd. A group of the same name will be created as well.";
       };
       webService = mkOption {
         type = types.bool;
         default = true;
-        description = "Enable a smokeping web interface";
+        description = lib.mdDoc "Enable a smokeping web interface";
       };
     };
 
@@ -316,6 +315,17 @@ 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} = {};
     systemd.services.smokeping = {
diff --git a/nixos/modules/services/networking/sniproxy.nix b/nixos/modules/services/networking/sniproxy.nix
index adca5398e4ab..b805b7b44d72 100644
--- a/nixos/modules/services/networking/sniproxy.nix
+++ b/nixos/modules/services/networking/sniproxy.nix
@@ -18,24 +18,24 @@ in
 
   options = {
     services.sniproxy = {
-      enable = mkEnableOption "sniproxy server";
+      enable = mkEnableOption (lib.mdDoc "sniproxy server");
 
       user = mkOption {
         type = types.str;
         default = "sniproxy";
-        description = "User account under which sniproxy runs.";
+        description = lib.mdDoc "User account under which sniproxy runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "sniproxy";
-        description = "Group under which sniproxy runs.";
+        description = lib.mdDoc "Group under which sniproxy runs.";
       };
 
       config = mkOption {
         type = types.lines;
         default = "";
-        description = "sniproxy.conf configuration excluding the daemon username and pid file.";
+        description = lib.mdDoc "sniproxy.conf configuration excluding the daemon username and pid file.";
         example = ''
           error_log {
             filename /var/log/sniproxy/error.log
diff --git a/nixos/modules/services/networking/snowflake-proxy.nix b/nixos/modules/services/networking/snowflake-proxy.nix
index 2124644ed9b5..ca015ed9d44b 100644
--- a/nixos/modules/services/networking/snowflake-proxy.nix
+++ b/nixos/modules/services/networking/snowflake-proxy.nix
@@ -8,28 +8,28 @@ in
 {
   options = {
     services.snowflake-proxy = {
-      enable = mkEnableOption "System to defeat internet censorship";
+      enable = mkEnableOption (lib.mdDoc "System to defeat internet censorship");
 
       broker = mkOption {
-        description = "Broker URL (default \"https://snowflake-broker.torproject.net/\")";
+        description = lib.mdDoc "Broker URL (default \"https://snowflake-broker.torproject.net/\")";
         type = with types; nullOr str;
         default = null;
       };
 
       capacity = mkOption {
-        description = "Limits the amount of maximum concurrent clients allowed.";
+        description = lib.mdDoc "Limits the amount of maximum concurrent clients allowed.";
         type = with types; nullOr int;
         default = null;
       };
 
       relay = mkOption {
-        description = "websocket relay URL (default \"wss://snowflake.bamsoftware.com/\")";
+        description = lib.mdDoc "websocket relay URL (default \"wss://snowflake.bamsoftware.com/\")";
         type = with types; nullOr str;
         default = null;
       };
 
       stun = mkOption {
-        description = "STUN broker URL (default \"stun:stun.stunprotocol.org:3478\")";
+        description = lib.mdDoc "STUN broker URL (default \"stun:stun.stunprotocol.org:3478\")";
         type = with types; nullOr str;
         default = null;
       };
@@ -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/nixos/modules/services/networking/softether.nix b/nixos/modules/services/networking/softether.nix
index 5405f56871e9..c8e888eafcc2 100644
--- a/nixos/modules/services/networking/softether.nix
+++ b/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,34 +16,34 @@ in
 
     services.softether = {
 
-      enable = mkEnableOption "SoftEther VPN services";
+      enable = mkEnableOption (lib.mdDoc "SoftEther VPN services");
 
       package = mkOption {
         type = types.package;
         default = pkgs.softether;
         defaultText = literalExpression "pkgs.softether";
-        description = ''
+        description = lib.mdDoc ''
           softether derivation to use.
         '';
       };
 
-      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 = "";
-          description = ''
+          description = lib.mdDoc ''
             Shell commands executed when the Virtual Network Adapter(s) is/are starting.
           '';
         };
         down = mkOption {
           type = types.lines;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Shell commands executed when the Virtual Network Adapter(s) is/are shutting down.
           '';
         };
@@ -52,7 +52,7 @@ in
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/softether";
-        description = ''
+        description = lib.mdDoc ''
           Data directory for SoftEther VPN.
         '';
       };
@@ -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/nixos/modules/services/networking/soju.nix b/nixos/modules/services/networking/soju.nix
index cb0acf4765ff..d4c4ca47bc80 100644
--- a/nixos/modules/services/networking/soju.nix
+++ b/nixos/modules/services/networking/soju.nix
@@ -27,16 +27,15 @@ in
   ###### interface
 
   options.services.soju = {
-    enable = mkEnableOption "soju";
+    enable = mkEnableOption (lib.mdDoc "soju");
 
     listen = mkOption {
       type = types.listOf types.str;
       default = [ ":6697" ];
-      description = ''
+      description = lib.mdDoc ''
         Where soju should listen for incoming connections. See the
-        <literal>listen</literal> directive in
-        <citerefentry><refentrytitle>soju</refentrytitle>
-        <manvolnum>1</manvolnum></citerefentry>.
+        `listen` directive in
+        {manpage}`soju(1)`.
       '';
     };
 
@@ -44,42 +43,43 @@ in
       type = types.str;
       default = config.networking.hostName;
       defaultText = literalExpression "config.networking.hostName";
-      description = "Server hostname.";
+      description = lib.mdDoc "Server hostname.";
     };
 
     tlsCertificate = mkOption {
       type = types.nullOr types.path;
+      default = null;
       example = "/var/host.cert";
-      description = "Path to server TLS certificate.";
+      description = lib.mdDoc "Path to server TLS certificate.";
     };
 
     tlsCertificateKey = mkOption {
       type = types.nullOr types.path;
+      default = null;
       example = "/var/host.key";
-      description = "Path to server TLS certificate key.";
+      description = lib.mdDoc "Path to server TLS certificate key.";
     };
 
     enableMessageLogging = mkOption {
       type = types.bool;
       default = true;
-      description = "Whether to enable message logging.";
+      description = lib.mdDoc "Whether to enable message logging.";
     };
 
     httpOrigins = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         List of allowed HTTP origins for WebSocket listeners. The parameters are
         interpreted as shell patterns, see
-        <citerefentry><refentrytitle>glob</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
+        {manpage}`glob(7)`.
       '';
     };
 
     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
@@ -90,13 +90,23 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = "Lines added verbatim to the configuration file.";
+      description = lib.mdDoc "Lines added verbatim to the configuration file.";
     };
   };
 
   ###### implementation
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.tlsCertificate != null) == (cfg.tlsCertificateKey != null);
+        message = ''
+          services.soju.tlsCertificate and services.soju.tlsCertificateKey
+          must both be specified to enable TLS.
+        '';
+      }
+    ];
+
     systemd.services.soju = {
       description = "soju IRC bouncer";
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/networking/solanum.nix b/nixos/modules/services/networking/solanum.nix
index dc066a245494..07a37279fecc 100644
--- a/nixos/modules/services/networking/solanum.nix
+++ b/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;
@@ -44,16 +44,16 @@ in
             default_split_user_count = 0;
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Solanum IRC daemon configuration file.
-          check <link xlink:href="https://github.com/solanum-ircd/solanum/blob/main/doc/reference.conf"/> for all options.
+          check <https://github.com/solanum-ircd/solanum/blob/main/doc/reference.conf> for all options.
         '';
       };
 
       openFilesLimit = mkOption {
         type = types.int;
         default = 1024;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of open files. Limits the clients and server connections.
         '';
       };
@@ -61,10 +61,10 @@ in
       motd = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Solanum MOTD text.
 
-          Solanum will read its MOTD from <literal>/etc/solanum/ircd.motd</literal>.
+          Solanum will read its MOTD from `/etc/solanum/ircd.motd`.
           If set, the value of this option will be written to this path.
         '';
       };
diff --git a/nixos/modules/services/networking/spacecookie.nix b/nixos/modules/services/networking/spacecookie.nix
index 400f3e26cc9a..b2956edfcb7f 100644
--- a/nixos/modules/services/networking/spacecookie.nix
+++ b/nixos/modules/services/networking/spacecookie.nix
@@ -25,14 +25,14 @@ in {
 
     services.spacecookie = {
 
-      enable = mkEnableOption "spacecookie";
+      enable = mkEnableOption (lib.mdDoc "spacecookie");
 
       package = mkOption {
         type = types.package;
         default = pkgs.spacecookie;
         defaultText = literalExpression "pkgs.spacecookie";
         example = literalExpression "pkgs.haskellPackages.spacecookie";
-        description = ''
+        description = lib.mdDoc ''
           The spacecookie derivation to use. This can be used to
           override the used package or to use another version.
         '';
@@ -41,7 +41,7 @@ in {
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to open the necessary port in the firewall for spacecookie.
         '';
       };
@@ -49,7 +49,7 @@ in {
       port = mkOption {
         type = types.port;
         default = 70;
-        description = ''
+        description = lib.mdDoc ''
           Port the gopher service should be exposed on.
         '';
       };
@@ -57,10 +57,10 @@ in {
       address = mkOption {
         type = types.str;
         default = "[::]";
-        description = ''
+        description = lib.mdDoc ''
           Address to listen on. Must be in the
-          <literal>ListenStream=</literal> syntax of
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.socket.html">systemd.socket(5)</link>.
+          `ListenStream=` syntax of
+          [systemd.socket(5)](https://www.freedesktop.org/software/systemd/man/systemd.socket.html).
         '';
       };
 
@@ -71,7 +71,7 @@ in {
           options.hostname = mkOption {
             type = types.str;
             default = "localhost";
-            description = ''
+            description = lib.mdDoc ''
               The hostname the service is reachable via. Clients
               will use this hostname for further requests after
               loading the initial gopher menu.
@@ -81,22 +81,22 @@ in {
           options.root = mkOption {
             type = types.path;
             default = "/srv/gopher";
-            description = ''
+            description = lib.mdDoc ''
               The directory spacecookie should serve via gopher.
               Files in there need to be world-readable since
               the spacecookie service file sets
-              <literal>DynamicUser=true</literal>.
+              `DynamicUser=true`.
             '';
           };
 
           options.log = {
-            enable = mkEnableOption "logging for spacecookie"
+            enable = mkEnableOption (lib.mdDoc "logging for spacecookie")
               // { default = true; example = false; };
 
             hide-ips = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 If enabled, spacecookie will hide personal
                 information of users like IP addresses from
                 log output.
@@ -110,7 +110,7 @@ in {
               # journald will add timestamps, so no need
               # to double up.
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 If enabled, spacecookie will not print timestamps
                 at the beginning of every log line.
               '';
@@ -123,18 +123,18 @@ in {
                 "error"
               ];
               default = "info";
-              description = ''
+              description = lib.mdDoc ''
                 Log level for the spacecookie service.
               '';
             };
           };
         };
 
-        description = ''
+        description = lib.mdDoc ''
           Settings for spacecookie. The settings set here are
           directly translated to the spacecookie JSON config
           file. See
-          <link xlink:href="https://sternenseemann.github.io/spacecookie/spacecookie.json.5.html">spacecookie.json(5)</link>
+          [spacecookie.json(5)](https://sternenseemann.github.io/spacecookie/spacecookie.json.5.html)
           for explanations of all options.
         '';
       };
diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix
index 3c229ecfc72e..3e01ace54ad1 100644
--- a/nixos/modules/services/networking/spiped.nix
+++ b/nixos/modules/services/networking/spiped.nix
@@ -11,7 +11,7 @@ in
       enable = mkOption {
         type        = types.bool;
         default     = false;
-        description = "Enable the spiped service module.";
+        description = lib.mdDoc "Enable the spiped service module.";
       };
 
       config = mkOption {
@@ -21,32 +21,32 @@ in
               encrypt = mkOption {
                 type    = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Take unencrypted connections from the
-                  <literal>source</literal> socket and send encrypted
-                  connections to the <literal>target</literal> socket.
+                  `source` socket and send encrypted
+                  connections to the `target` socket.
                 '';
               };
 
               decrypt = mkOption {
                 type    = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Take encrypted connections from the
-                  <literal>source</literal> socket and send unencrypted
-                  connections to the <literal>target</literal> socket.
+                  `source` socket and send unencrypted
+                  connections to the `target` socket.
                 '';
               };
 
               source = mkOption {
                 type    = types.str;
-                description = ''
+                description = lib.mdDoc ''
                   Address on which spiped should listen for incoming
                   connections.  Must be in one of the following formats:
-                  <literal>/absolute/path/to/unix/socket</literal>,
-                  <literal>host.name:port</literal>,
-                  <literal>[ip.v4.ad.dr]:port</literal> or
-                  <literal>[ipv6::addr]:port</literal> - note that
+                  `/absolute/path/to/unix/socket`,
+                  `host.name:port`,
+                  `[ip.v4.ad.dr]:port` or
+                  `[ipv6::addr]:port` - note that
                   hostnames are resolved when spiped is launched and are
                   not re-resolved later; thus if DNS entries change
                   spiped will continue to connect to the expired
@@ -56,24 +56,24 @@ in
 
               target = mkOption {
                 type    = types.str;
-                description = "Address to which spiped should connect.";
+                description = lib.mdDoc "Address to which spiped should connect.";
               };
 
               keyfile = mkOption {
                 type    = types.path;
-                description = ''
+                description = lib.mdDoc ''
                   Name of a file containing the spiped key. As the
-                  daemon runs as the <literal>spiped</literal> user, the
+                  daemon runs as the `spiped` user, the
                   key file must be somewhere owned by that user. By
                   default, we recommend putting the keys for any spipe
-                  services in <literal>/var/lib/spiped</literal>.
+                  services in `/var/lib/spiped`.
                 '';
               };
 
               timeout = mkOption {
                 type = types.int;
                 default = 5;
-                description = ''
+                description = lib.mdDoc ''
                   Timeout, in seconds, after which an attempt to connect to
                   the target or a protocol handshake will be aborted (and the
                   connection dropped) if not completed
@@ -83,7 +83,7 @@ in
               maxConns = mkOption {
                 type = types.int;
                 default = 100;
-                description = ''
+                description = lib.mdDoc ''
                   Limit on the number of simultaneous connections allowed.
                 '';
               };
@@ -91,14 +91,14 @@ in
               waitForDNS = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
-                  Wait for DNS. Normally when <literal>spiped</literal> is
+                description = lib.mdDoc ''
+                  Wait for DNS. Normally when `spiped` is
                   launched it resolves addresses and binds to its source
                   socket before the parent process returns; with this option
                   it will daemonize first and retry failed DNS lookups until
-                  they succeed. This allows <literal>spiped</literal> to
+                  they succeed. This allows `spiped` to
                   launch even if DNS isn't set up yet, but at the expense of
-                  losing the guarantee that once <literal>spiped</literal> has
+                  losing the guarantee that once `spiped` has
                   finished launching it will be ready to create pipes.
                 '';
               };
@@ -106,13 +106,13 @@ in
               disableKeepalives = mkOption {
                 type = types.bool;
                 default = false;
-                description = "Disable transport layer keep-alives.";
+                description = lib.mdDoc "Disable transport layer keep-alives.";
               };
 
               weakHandshake = mkOption {
                 type = types.bool;
                 default = false;
-                description = ''
+                description = lib.mdDoc ''
                   Use fast/weak handshaking: This reduces the CPU time spent
                   in the initial connection setup, at the expense of losing
                   perfect forward secrecy.
@@ -122,7 +122,7 @@ in
               resolveRefresh = mkOption {
                 type = types.int;
                 default = 60;
-                description = ''
+                description = lib.mdDoc ''
                   Resolution refresh time for the target socket, in seconds.
                 '';
               };
@@ -130,7 +130,7 @@ in
               disableReresolution = mkOption {
                 type = types.bool;
                 default = false;
-                description = "Disable target address re-resolution.";
+                description = lib.mdDoc "Disable target address re-resolution.";
               };
             };
           }
@@ -155,11 +155,11 @@ in
           }
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Configuration for a secure pipe daemon. The daemon can be
           started, stopped, or examined using
-          <literal>systemctl</literal>, under the name
-          <literal>spiped@foo</literal>.
+          `systemctl`, under the name
+          `spiped@foo`.
         '';
       };
     };
diff --git a/nixos/modules/services/networking/squid.nix b/nixos/modules/services/networking/squid.nix
index db4f0d26b6f4..914cd7f320c9 100644
--- a/nixos/modules/services/networking/squid.nix
+++ b/nixos/modules/services/networking/squid.nix
@@ -108,32 +108,32 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to run squid web proxy.";
+        description = lib.mdDoc "Whether to run squid web proxy.";
       };
 
       package = mkOption {
         default = pkgs.squid;
         defaultText = literalExpression "pkgs.squid";
         type = types.package;
-        description = "Squid package to use.";
+        description = lib.mdDoc "Squid package to use.";
       };
 
       proxyAddress = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "IP address on which squid will listen.";
+        description = lib.mdDoc "IP address on which squid will listen.";
       };
 
       proxyPort = mkOption {
         type = types.int;
         default = 3128;
-        description = "TCP port on which squid will listen.";
+        description = lib.mdDoc "TCP port on which squid will listen.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Squid configuration. Contents will be added
           verbatim to the configuration file.
         '';
@@ -142,7 +142,7 @@ in
       configText = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Verbatim contents of squid.conf. If null (default), use the
           autogenerated file from NixOS instead.
         '';
diff --git a/nixos/modules/services/networking/ssh/lshd.nix b/nixos/modules/services/networking/ssh/lshd.nix
index 862ff7df0540..41c4ec2d2951 100644
--- a/nixos/modules/services/networking/ssh/lshd.nix
+++ b/nixos/modules/services/networking/ssh/lshd.nix
@@ -21,7 +21,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the GNU lshd SSH2 daemon, which allows
           secure remote login.
         '';
@@ -30,7 +30,7 @@ in
       portNumber = mkOption {
         default = 22;
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           The port on which to listen for connections.
         '';
       };
@@ -38,7 +38,7 @@ in
       interfaces = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of network interfaces where listening for connections.
           When providing the empty list, `[]', lshd listens on all
           network interfaces.
@@ -49,7 +49,7 @@ in
       hostKey = mkOption {
         default = "/etc/lsh/host-key";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Path to the server's private key.  Note that this key must
           have been created, e.g., using "lsh-keygen --server |
           lsh-writekey --server", so that you can run lshd.
@@ -59,31 +59,31 @@ in
       syslog = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable syslog output.";
+        description = lib.mdDoc "Whether to enable syslog output.";
       };
 
       passwordAuthentication = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable password authentication.";
+        description = lib.mdDoc "Whether to enable password authentication.";
       };
 
       publicKeyAuthentication = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable public key authentication.";
+        description = lib.mdDoc "Whether to enable public key authentication.";
       };
 
       rootLogin = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable remote root login.";
+        description = lib.mdDoc "Whether to enable remote root login.";
       };
 
       loginShell = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           If non-null, override the default login shell with the
           specified value.
         '';
@@ -93,7 +93,7 @@ in
       srpKeyExchange = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable SRP key exchange and user authentication.
         '';
       };
@@ -101,18 +101,18 @@ in
       tcpForwarding = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable TCP/IP forwarding.";
+        description = lib.mdDoc "Whether to enable TCP/IP forwarding.";
       };
 
       x11Forwarding = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable X11 forwarding.";
+        description = lib.mdDoc "Whether to enable X11 forwarding.";
       };
 
       subsystems = mkOption {
         type = types.listOf types.path;
-        description = ''
+        description = lib.mdDoc ''
           List of subsystem-path pairs, where the head of the pair
           denotes the subsystem name, and the tail denotes the path to
           an executable implementing it.
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 230ab673a976..af8200c7e295 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -32,13 +32,13 @@ let
       keys = mkOption {
         type = types.listOf types.singleLineStr;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of verbatim OpenSSH public keys that should be added to the
           user's authorized keys. The keys are added to a file that the SSH
           daemon reads in addition to the the user's authorized_keys file.
-          You can combine the <literal>keys</literal> and
-          <literal>keyFiles</literal> options.
-          Warning: If you are using <literal>NixOps</literal> then don't use this
+          You can combine the `keys` and
+          `keyFiles` options.
+          Warning: If you are using `NixOps` then don't use this
           option since it will replace the key required for deployment via ssh.
         '';
         example = [
@@ -50,12 +50,12 @@ let
       keyFiles = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of files each containing one OpenSSH public key that should be
           added to the user's authorized keys. The contents of the files are
           read at build time and added to a file that the SSH daemon reads in
           addition to the the user's authorized_keys file. You can combine the
-          <literal>keyFiles</literal> and <literal>keys</literal> options.
+          `keyFiles` and `keys` options.
         '';
       };
     };
@@ -93,7 +93,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the OpenSSH secure shell daemon, which
           allows secure remote logins.
         '';
@@ -102,8 +102,8 @@ in
       startWhenNeeded = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          If set, <command>sshd</command> is socket-activated; that
+        description = lib.mdDoc ''
+          If set, {command}`sshd` is socket-activated; that
           is, instead of having it permanently running as a daemon,
           systemd will start an instance for each incoming connection.
         '';
@@ -112,7 +112,7 @@ in
       forwardX11 = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow X11 connections to be forwarded.
         '';
       };
@@ -120,17 +120,17 @@ in
       allowSFTP = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the SFTP subsystem in the SSH daemon.  This
-          enables the use of commands such as <command>sftp</command> and
-          <command>sshfs</command>.
+          enables the use of commands such as {command}`sftp` and
+          {command}`sshfs`.
         '';
       };
 
       sftpServerExecutable = mkOption {
         type = types.str;
         example = "internal-sftp";
-        description = ''
+        description = lib.mdDoc ''
           The sftp server executable.  Can be a path or "internal-sftp" to use
           the sftp server built into the sshd binary.
         '';
@@ -140,7 +140,7 @@ in
         type = with types; listOf str;
         default = [];
         example = [ "-f AUTHPRIV" "-l INFO" ];
-        description = ''
+        description = lib.mdDoc ''
           Commandline flags to add to sftp-server.
         '';
       };
@@ -148,7 +148,7 @@ in
       permitRootLogin = mkOption {
         default = "prohibit-password";
         type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
-        description = ''
+        description = lib.mdDoc ''
           Whether the root user can login using ssh.
         '';
       };
@@ -156,18 +156,17 @@ in
       gatewayPorts = mkOption {
         type = types.str;
         default = "no";
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether remote hosts are allowed to connect to
           ports forwarded for the client.  See
-          <citerefentry><refentrytitle>sshd_config</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry>.
+          {manpage}`sshd_config(5)`.
         '';
       };
 
       ports = mkOption {
         type = types.listOf types.port;
         default = [22];
-        description = ''
+        description = lib.mdDoc ''
           Specifies on which ports the SSH daemon listens.
         '';
       };
@@ -175,7 +174,7 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to automatically open the specified ports in the firewall.
         '';
       };
@@ -186,14 +185,14 @@ in
             addr = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Host, IPv4 or IPv6 address to listen to.
               '';
             };
             port = mkOption {
               type = types.nullOr types.int;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Port to listen to.
               '';
             };
@@ -201,10 +200,10 @@ in
         });
         default = [];
         example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ];
-        description = ''
+        description = lib.mdDoc ''
           List of addresses and ports to listen on (ListenAddress directive
           in config). If port is not specified for address sshd will listen
-          on all ports specified by <literal>ports</literal> option.
+          on all ports specified by `ports` option.
           NOTE: this will override default listening on all local addresses and port 22.
           NOTE: setting this option won't automatically enable given ports
           in firewall configuration.
@@ -214,7 +213,7 @@ in
       passwordAuthentication = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether password authentication is allowed.
         '';
       };
@@ -222,7 +221,7 @@ in
       kbdInteractiveAuthentication = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether keyboard-interactive authentication is allowed.
         '';
       };
@@ -237,11 +236,10 @@ in
           [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; rounds = 100; openSSHFormat = true; }
             { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; rounds = 100; comment = "key comment"; }
           ];
-        description = ''
+        description = lib.mdDoc ''
           NixOS can automatically generate SSH host keys.  This option
           specifies the path, type and size of each key.  See
-          <citerefentry><refentrytitle>ssh-keygen</refentrytitle>
-          <manvolnum>1</manvolnum></citerefentry> for supported types
+          {manpage}`ssh-keygen(1)` for supported types
           and sizes.
         '';
       };
@@ -249,7 +247,7 @@ in
       banner = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Message to display to the remote user before authentication is allowed.
         '';
       };
@@ -257,12 +255,12 @@ in
       authorizedKeysFiles = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Specify the rules for which files to read on the host.
 
           This is an advanced option. If you're looking to configure user
-          keys, you can generally use <xref linkend="opt-users.users._name_.openssh.authorizedKeys.keys"/>
-          or <xref linkend="opt-users.users._name_.openssh.authorizedKeys.keyFiles"/>.
+          keys, you can generally use [](#opt-users.users._name_.openssh.authorizedKeys.keys)
+          or [](#opt-users.users._name_.openssh.authorizedKeys.keyFiles).
 
           These are paths relative to the host root file system or home
           directories and they are subject to certain token expansion rules.
@@ -273,7 +271,7 @@ in
       authorizedKeysCommand = mkOption {
         type = types.str;
         default = "none";
-        description = ''
+        description = lib.mdDoc ''
           Specifies a program to be used to look up the user's public
           keys. The program must be owned by root, not writable by group
           or others and specified by an absolute path.
@@ -283,7 +281,7 @@ in
       authorizedKeysCommandUser = mkOption {
         type = types.str;
         default = "nobody";
-        description = ''
+        description = lib.mdDoc ''
           Specifies the user under whose account the AuthorizedKeysCommand
           is run. It is recommended to use a dedicated user that has no
           other role on the host than running authorized keys commands.
@@ -293,18 +291,18 @@ 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 = ''
+        description = lib.mdDoc ''
           Allowed key exchange algorithms
-          </para>
-          <para>
-          Defaults to recommended settings from both
-          <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+
+          Uses the lower bound recommended in both
+          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
           and
-          <link xlink:href="https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67" />
+          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
         '';
       };
 
@@ -318,14 +316,13 @@ in
           "aes192-ctr"
           "aes128-ctr"
         ];
-        description = ''
+        description = lib.mdDoc ''
           Allowed ciphers
-          </para>
-          <para>
+
           Defaults to recommended settings from both
-          <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
           and
-          <link xlink:href="https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67" />
+          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
         '';
       };
 
@@ -339,21 +336,20 @@ in
           "hmac-sha2-256"
           "umac-128@openssh.com"
         ];
-        description = ''
+        description = lib.mdDoc ''
           Allowed MACs
-          </para>
-          <para>
+
           Defaults to recommended settings from both
-          <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+          <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
           and
-          <link xlink:href="https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67" />
+          <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
         '';
       };
 
       logLevel = mkOption {
         type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
         default = "INFO"; # upstream default
-        description = ''
+        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
@@ -364,7 +360,7 @@ in
       useDns = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        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
@@ -375,16 +371,16 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Verbatim contents of <filename>sshd_config</filename>.";
+        description = lib.mdDoc "Verbatim contents of {file}`sshd_config`.";
       };
 
       moduliFile = mkOption {
         example = "/etc/my-local-ssh-moduli;";
         type = types.path;
-        description = ''
-          Path to <literal>moduli</literal> file to install in
-          <literal>/etc/ssh/moduli</literal>. If this option is unset, then
-          the <literal>moduli</literal> file shipped with OpenSSH will be used.
+        description = lib.mdDoc ''
+          Path to `moduli` file to install in
+          `/etc/ssh/moduli`. If this option is unset, then
+          the `moduli` file shipped with OpenSSH will be used.
         '';
       };
 
@@ -437,10 +433,12 @@ in
                 # socket activation, it goes to the remote side (#19589).
                 exec >&2
 
-                mkdir -m 0755 -p /etc/ssh
-
                 ${flip concatMapStrings cfg.hostKeys (k: ''
                   if ! [ -s "${k.path}" ]; then
+                      if ! [ -h "${k.path}" ]; then
+                          rm -f "${k.path}"
+                      fi
+                      mkdir -m 0755 -p "$(dirname '${k.path}')"
                       ssh-keygen \
                         -t "${k.type}" \
                         ${if k ? bits then "-b ${toString k.bits}" else ""} \
diff --git a/nixos/modules/services/networking/sslh.nix b/nixos/modules/services/networking/sslh.nix
index abe96f60f811..daf2f2f3668e 100644
--- a/nixos/modules/services/networking/sslh.nix
+++ b/nixos/modules/services/networking/sslh.nix
@@ -43,42 +43,42 @@ in
 
   options = {
     services.sslh = {
-      enable = mkEnableOption "sslh";
+      enable = mkEnableOption (lib.mdDoc "sslh");
 
       verbose = mkOption {
         type = types.bool;
         default = false;
-        description = "Verbose logs.";
+        description = lib.mdDoc "Verbose logs.";
       };
 
       timeout = mkOption {
         type = types.int;
         default = 2;
-        description = "Timeout in seconds.";
+        description = lib.mdDoc "Timeout in seconds.";
       };
 
       transparent = mkOption {
         type = types.bool;
         default = false;
-        description = "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them";
+        description = lib.mdDoc "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them";
       };
 
       listenAddresses = mkOption {
         type = types.coercedTo types.str singleton (types.listOf types.str);
         default = [ "0.0.0.0" "[::]" ];
-        description = "Listening addresses or hostnames.";
+        description = lib.mdDoc "Listening addresses or hostnames.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 443;
-        description = "Listening port.";
+        description = lib.mdDoc "Listening port.";
       };
 
       appendConfig = mkOption {
         type = types.str;
         default = defaultAppendConfig;
-        description = "Verbatim configuration file.";
+        description = lib.mdDoc "Verbatim configuration file.";
       };
     };
   };
diff --git a/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixos/modules/services/networking/strongswan-swanctl/module.nix
index 9287943fcde3..c51e8ad9f5fc 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/module.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -8,13 +8,13 @@ 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;
       default = pkgs.strongswan;
       defaultText = literalExpression "pkgs.strongswan";
-      description = ''
+      description = lib.mdDoc ''
         The strongswan derivation to use.
       '';
     };
@@ -22,8 +22,8 @@ in  {
     strongswan.extraConfig = mkOption {
       type = types.str;
       default = "";
-      description = ''
-        Contents of the <literal>strongswan.conf</literal> file.
+      description = lib.mdDoc ''
+        Contents of the `strongswan.conf` file.
       '';
     };
 
diff --git a/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
index dfdfc50d8ae2..dc6d8f48e626 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
@@ -57,11 +57,12 @@ rec {
 
   documentDefault = description : strongswanDefault :
     if strongswanDefault == null
-    then description
-    else description + ''
-      </para><para>
-      StrongSwan default: <literal><![CDATA[${builtins.toJSON strongswanDefault}]]></literal>
-    '';
+    then mdDoc description
+    else mdDoc (description + ''
+
+
+      StrongSwan default: ````${builtins.toJSON strongswanDefault}````
+    '');
 
   single = f: name: value: { ${name} = f value; };
 
@@ -120,7 +121,7 @@ rec {
     option = mkOption {
       type = types.attrsOf option;
       default = {};
-      inherit description;
+      description = mdDoc description;
     };
     render = single (attrs:
       (paramsToRenderedStrings attrs
@@ -138,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;
@@ -151,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/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index cca61b9ce930..84ac4fef26ef 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -15,15 +15,15 @@ let
     file = mkOptionalStrParam ''
       Absolute path to the certificate to load. Passed as-is to the daemon, so
       it must be readable by it.
-      </para><para>
-      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.
-      </para><para>
-      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.
-      </para><para>
-      Configure one of <option>cacert</option>,
-      <option>file</option>, or
-      <option>handle</option> per section.
+      `x509ca` directory or an absolute path.
+
+      Configure one of {option}`cacert`,
+      {option}`file`, or
+      {option}`handle` per section.
     '';
 
     cert_uri_base = mkOptionalStrParam ''
@@ -71,22 +71,21 @@ 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 [] ''
       Local address(es) to use for IKE communication. Takes
       single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges.
-      </para><para>
+
       As initiator, the first non-range/non-subnet is used to initiate the
       connection from. As responder, the local destination address must match at
       least to one of the specified addresses, subnets or ranges.
-      </para><para>
+
       If FQDNs are assigned they are resolved every time a configuration lookup
       is done. If DNS resolution times out, the lookup is delayed for that time.
     '';
@@ -94,11 +93,11 @@ in {
     remote_addrs = mkCommaSepListParam [] ''
       Remote address(es) to use for IKE communication. Takes
       single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges.
-      </para><para>
+
       As initiator, the first non-range/non-subnet is used to initiate the
       connection to. As responder, the initiator source address must match at
       least to one of the specified addresses, subnets or ranges.
-      </para><para>
+
       If FQDNs are assigned they are resolved every time a configuration lookup
       is done. If DNS resolution times out, the lookup is delayed for that time.
       To initiate a connection, at least one specific address or DNS name must
@@ -107,18 +106,18 @@ 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.
-      </para><para>
+      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).
     '';
 
     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"] ''
@@ -126,23 +125,23 @@ in {
       for IKE an encryption algorithm, an integrity algorithm, a pseudo random
       function and a Diffie-Hellman group. For AEAD algorithms, instead of
       encryption and integrity algorithms, a combined algorithm is used.
-      </para><para>
+
       In IKEv2, multiple algorithms of the same kind can be specified in a
       single proposal, from which one gets selected. In IKEv1, only one
       algorithm per kind is allowed per proposal, more algorithms get implicitly
       stripped. Use multiple proposals to offer different algorithms
       combinations in IKEv1.
-      </para><para>
+
       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.
     '';
@@ -159,7 +158,7 @@ in {
       If the default of yes is used, Mode Config works in pull mode, where the
       initiator actively requests a virtual IP. With no, push mode is used,
       where the responder pushes down a virtual IP to the initiating peer.
-      </para><para>
+
       Push mode is currently supported for IKEv1, but not in IKEv2. It is used
       by a few implementations only, pull mode is recommended.
     '';
@@ -174,7 +173,7 @@ in {
       To enforce UDP encapsulation of ESP packets, the IKE daemon can fake the
       NAT detection payloads. This makes the peer believe that NAT takes place
       on the path, forcing it to encapsulate ESP packets in UDP.
-      </para><para>
+
       Usually this is not required, but it can help to work around connectivity
       issues with too restrictive intermediary firewalls.
     '';
@@ -183,7 +182,7 @@ in {
       Enables MOBIKE on IKEv2 connections. MOBIKE is enabled by default on IKEv2
       connections, and allows mobility of clients and multi-homing on servers by
       migrating active IPsec tunnels.
-      </para><para>
+
       Usually keeping MOBIKE enabled is unproblematic, as it is not used if the
       peer does not indicate support for it. However, due to the design of
       MOBIKE, IKEv2 always floats to port 4500 starting from the second
@@ -207,39 +206,38 @@ 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>
-      </para><para>
+      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
+      are `allow` (the default), `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
+      `force`, 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
+      setting the option to `never` disables support for
       childless IKE_SAs as responder.
     '';
 
@@ -254,14 +252,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,42 +272,33 @@ 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.
     '';
 
     unique = mkEnumParam ["no" "never" "keep" "replace"] "no" ''
       Connection uniqueness policy to enforce. To avoid multiple connections
       from the same user, a uniqueness policy can be enforced.
-      </para><para>
-      <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.
-      </para><para>
+
       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" ''
@@ -320,7 +308,7 @@ in {
       possible to actively reauthenticate as responder. The IKEv2
       reauthentication lifetime negotiation can instruct the client to perform
       reauthentication.
-      </para><para>
+
       Reauthentication is disabled by default. Enabling it usually may lead to
       small connection interruptions, as strongSwan uses a break-before-make
       policy with IKEv2 to avoid any conflicts with associated tunnel resources.
@@ -330,7 +318,7 @@ in {
       IKE rekeying refreshes key material using a Diffie-Hellman exchange, but
       does not re-check associated credentials. It is supported in IKEv2 only,
       IKEv1 performs a reauthentication procedure instead.
-      </para><para>
+
       With the default value IKE rekeying is scheduled every 4 hours, minus the
       configured rand_time. If a reauth_time is configured, rekey_time defaults
       to zero, disabling rekeying; explicitly set both to enforce rekeying and
@@ -343,12 +331,12 @@ in {
       perpetually, a maximum hard lifetime may be specified. If the IKE_SA fails
       to rekey or reauthenticate within the specified time, the IKE_SA gets
       closed.
-      </para><para>
+
       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.
-      </para><para>
-      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 ''
@@ -356,8 +344,8 @@ in {
       rekey/reauth times. To avoid having both peers initiating the rekey/reauth
       procedure simultaneously, a random time gets subtracted from the
       rekey/reauth times.
-      </para><para>
-      The default is equal to the configured <option>over_time</option>.
+
+      The default is equal to the configured {option}`over_time`.
     '';
 
     pools = mkCommaSepListParam [] ''
@@ -409,8 +397,8 @@ 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.
-        </para><para>
+        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
         located, the first certificate is used.
@@ -425,67 +413,52 @@ 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.
-        </para><para>
+        `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.
       '';
 
       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 ''
@@ -504,7 +477,7 @@ in {
         authentication. This identity may differ from the IKE identity,
         especially when EAP authentication is delegated from the IKE responder
         to an AAA backend.
-        </para><para>
+
         For EAP-(T)TLS, this defines the identity for which the server must
         provide a certificate in the TLS exchange.
       '';
@@ -518,8 +491,8 @@ in {
       defines the rules how authentication is performed for the local
       peer. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
       Authentication or IKEv1 XAuth.
-      </para><para>
-      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 +513,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 +532,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 +546,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 +555,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 +567,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.
-        </para><para>
+
         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).
-        </para><para>
+        `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`).
       '';
 
     } ''
@@ -652,8 +618,8 @@ in {
       defines the constraints how the peers must authenticate to use this
       connection. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
       Authentication or IKEv1 XAuth.
-      </para><para>
-      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.
     '';
@@ -665,15 +631,15 @@ in {
         Diffie-Hellman group. If a DH group is specified, CHILD_SA/Quick Mode
         rekeying and initial negotiation uses a separate Diffie-Hellman exchange
         using the specified group (refer to esp_proposals for details).
-        </para><para>
+
         In IKEv2, multiple algorithms of the same kind can be specified in a
         single proposal, from which one gets selected. In IKEv1, only one
         algorithm per kind is allowed per proposal, more algorithms get
         implicitly stripped. Use multiple proposals to offer different algorithms
         combinations in IKEv1.
-        </para><para>
+
         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.
@@ -686,7 +652,7 @@ in {
         an optional Extended Sequence Number Mode indicator. For AEAD proposals,
         a combined mode algorithm is used instead of the separate
         encryption/integrity algorithms.
-        </para><para>
+
         If a DH group is specified, CHILD_SA/Quick Mode rekeying and initial
         negotiation use a separate Diffie-Hellman exchange using the specified
         group. However, for IKEv2, the keys of the CHILD_SA created implicitly
@@ -695,20 +661,20 @@ in {
         rekeyed or is created with a separate CREATE_CHILD_SA exchange. A
         proposal mismatch might, therefore, not immediately be noticed when the
         SA is established, but may later cause rekeying to fail.
-        </para><para>
+
         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.
-        </para><para>
+        `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
         algorithm per kind is allowed per proposal, more algorithms get
         implicitly stripped. Use multiple proposals to offer different algorithms
         combinations in IKEv1.
-        </para><para>
+
         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,19 +692,19 @@ 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.
-        </para><para>
+
         A protocol/port selector is surrounded by opening and closing square
         brackets. Between these brackets, a numeric or getservent(3) protocol
         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.
-        </para><para>
+
         When IKEv1 is used only the first selector is interpreted, except if the
         Cisco Unity extension plugin is used. This is due to a limitation of the
         IKEv1 protocol, which only allows a single pair of selectors per
@@ -752,42 +718,42 @@ 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.
-        </para><para>
+
         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 ''
         Number of bytes processed before initiating CHILD_SA rekeying. CHILD_SA
         rekeying refreshes key material, optionally using a Diffie-Hellman
         exchange if a group is specified in the proposal.
-        </para><para>
+
         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.
-        </para><para>
+
         Volume based CHILD_SA rekeying is disabled by default.
       '';
 
@@ -795,24 +761,24 @@ 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 ''
         Number of packets processed before initiating CHILD_SA rekeying. CHILD_SA
         rekeying refreshes key material, optionally using a Diffie-Hellman
         exchange if a group is specified in the proposal.
-        </para><para>
+
         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.
-        </para><para>
+
         Packet count based CHILD_SA rekeying is disabled by default.
       '';
 
@@ -821,14 +787,14 @@ in {
         this hard packets limit is never reached, because the CHILD_SA gets
         rekeyed before. If that fails for whatever reason, this limit closes the
         CHILD_SA.
-        </para><para>
-        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 +802,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 +813,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 +885,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
-        </para><para>
+
         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 +910,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).
-        </para><para>
+
         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 +926,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 +940,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 +952,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,23 +973,23 @@ 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" ''
         Enable hardware offload for this CHILD_SA, if supported by the IPsec
-        implementation. The value <literal>yes</literal> enforces offloading
+        implementation. The value `yes` 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
+        device. The value `auto` enables offloading, if it's
         supported, but the installation does not fail otherwise.
       '';
 
@@ -1055,55 +1008,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>
-        </para><para>
-        <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 +1051,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 +1070,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 +1100,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 +1113,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 +1149,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 +1175,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 +1188,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 +1201,7 @@ in {
       '';
     } ''
       PKCS#12 decryption passphrase for a container in the
-      <literal>pkcs12</literal> folder.
+      `pkcs12` folder.
     '';
 
     token = mkPrefixedAttrsOfParams {
@@ -1281,7 +1221,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 +1231,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 +1245,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/nixos/modules/services/networking/strongswan.nix b/nixos/modules/services/networking/strongswan.nix
index e3a97207be7f..8b1398bfd47d 100644
--- a/nixos/modules/services/networking/strongswan.nix
+++ b/nixos/modules/services/networking/strongswan.nix
@@ -51,16 +51,16 @@ let
 in
 {
   options.services.strongswan = {
-    enable = mkEnableOption "strongSwan";
+    enable = mkEnableOption (lib.mdDoc "strongSwan");
 
     secrets = mkOption {
       type = types.listOf types.str;
       default = [];
       example = [ "/run/keys/ipsec-foo.secret" ];
-      description = ''
+      description = lib.mdDoc ''
         A list of paths to IPSec secret files. These
         files will be included into the main ipsec.secrets file with
-        the <literal>include</literal> directive. It is safer if these
+        the `include` directive. It is safer if these
         paths are absolute.
       '';
     };
@@ -69,9 +69,9 @@ in
       type = types.attrsOf types.str;
       default = {};
       example = { cachecrls = "yes"; strictcrlpolicy = "yes"; };
-      description = ''
+      description = lib.mdDoc ''
         A set of options for the ‘config setup’ section of the
-        <filename>ipsec.conf</filename> file. Defines general
+        {file}`ipsec.conf` file. Defines general
         configuration parameters.
       '';
     };
@@ -94,9 +94,9 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         A set of connections and their options for the ‘conn xxx’
-        sections of the <filename>ipsec.conf</filename> file.
+        sections of the {file}`ipsec.conf` file.
       '';
     };
 
@@ -110,9 +110,9 @@ in
           crluri = "http://crl2.strongswan.org/strongswan.crl";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         A set of CAs (certification authorities) and their options for
-        the ‘ca xxx’ sections of the <filename>ipsec.conf</filename>
+        the ‘ca xxx’ sections of the {file}`ipsec.conf`
         file.
       '';
     };
@@ -120,19 +120,19 @@ in
     managePlugins = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If set to true, this option will disable automatic plugin loading and
         then tell strongSwan to enable the plugins specified in the
-        <option>enabledPlugins</option> option.
+        {option}`enabledPlugins` option.
       '';
     };
 
     enabledPlugins = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         A list of additional plugins to enable if
-        <option>managePlugins</option> is true.
+        {option}`managePlugins` is true.
       '';
     };
   };
diff --git a/nixos/modules/services/networking/stubby.nix b/nixos/modules/services/networking/stubby.nix
index 78c13798dde2..183002ff72b9 100644
--- a/nixos/modules/services/networking/stubby.nix
+++ b/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;
@@ -39,20 +41,32 @@ in {
             }];
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Content of the Stubby configuration file. All Stubby settings may be set or queried
           here. The default settings are available at
-          <literal>pkgs.stubby.passthru.settingsExample</literal>. See
-          <link xlink:href="https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby"/>.
+          `pkgs.stubby.passthru.settingsExample`. See
+          <https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby>.
           A list of the public recursive servers can be found here:
-          <link xlink:href="https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers"/>.
+          <https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers>.
         '';
       };
 
-      debugLogging = mkOption {
-        default = false;
-        type = types.bool;
-        description = "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/nixos/modules/services/networking/stunnel.nix b/nixos/modules/services/networking/stunnel.nix
index df4908a0fff9..4f592fb312d3 100644
--- a/nixos/modules/services/networking/stunnel.nix
+++ b/nixos/modules/services/networking/stunnel.nix
@@ -7,80 +7,27 @@ let
   cfg = config.services.stunnel;
   yesNo = val: if val then "yes" else "no";
 
+  verifyRequiredField = type: field: n: c: {
+    assertion = hasAttr field c;
+    message =  "stunnel: \"${n}\" ${type} configuration - Field ${field} is required.";
+  };
+
   verifyChainPathAssert = n: c: {
-    assertion = c.verifyHostname == null || (c.verifyChain || c.verifyPeer);
+    assertion = (c.verifyHostname or null) == null || (c.verifyChain || c.verifyPeer);
     message =  "stunnel: \"${n}\" client configuration - hostname verification " +
       "is not possible without either verifyChain or verifyPeer enabled";
   };
 
-  serverConfig = {
-    options = {
-      accept = mkOption {
-        type = types.either types.str types.int;
-        description = ''
-          On which [host:]port stunnel should listen for incoming TLS connections.
-          Note that unlike other softwares stunnel ipv6 address need no brackets,
-          so to listen on all IPv6 addresses on port 1234 one would use ':::1234'.
-        '';
-      };
-
-      connect = mkOption {
-        type = types.either types.str types.int;
-        description = "Port or IP:Port to which the decrypted connection should be forwarded.";
-      };
-
-      cert = mkOption {
-        type = types.path;
-        description = "File containing both the private and public keys.";
-      };
-    };
-  };
-
-  clientConfig = {
-    options = {
-      accept = mkOption {
-        type = types.str;
-        description = "IP:Port on which connections should be accepted.";
-      };
-
-      connect = mkOption {
-        type = types.str;
-        description = "IP:Port destination to connect to.";
-      };
-
-      verifyChain = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Check if the provided certificate has a valid certificate chain (against CAPath).";
-      };
-
-      verifyPeer = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Check if the provided certificate is contained in CAPath.";
-      };
-
-      CAPath = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = "Path to a directory containing certificates to validate against.";
-      };
-
-      CAFile = mkOption {
-        type = types.nullOr types.path;
-        default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
-        defaultText = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
-        description = "Path to a file containing certificates to validate against.";
-      };
-
-      verifyHostname = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        description = "If set, stunnel checks if the provided certificate is valid for the given hostname.";
-      };
-    };
-  };
-
+  removeNulls = mapAttrs (_: filterAttrs (_: v: v != null));
+  mkValueString = v:
+    if v == true then "yes"
+    else if v == false then "no"
+    else generators.mkValueStringDefault {} v;
+  generateConfig = c:
+    generators.toINI {
+      mkSectionName = id;
+      mkKeyValue = k: v: "${k} = ${mkValueString v}";
+    } (removeNulls c);
 
 in
 
@@ -95,43 +42,47 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the stunnel TLS tunneling service.";
+        description = lib.mdDoc "Whether to enable the stunnel TLS tunneling service.";
       };
 
       user = mkOption {
         type = with types; nullOr str;
         default = "nobody";
-        description = "The user under which stunnel runs.";
+        description = lib.mdDoc "The user under which stunnel runs.";
       };
 
       group = mkOption {
         type = with types; nullOr str;
         default = "nogroup";
-        description = "The group under which stunnel runs.";
+        description = lib.mdDoc "The group under which stunnel runs.";
       };
 
       logLevel = mkOption {
         type = types.enum [ "emerg" "alert" "crit" "err" "warning" "notice" "info" "debug" ];
         default = "info";
-        description = "Verbosity of stunnel output.";
+        description = lib.mdDoc "Verbosity of stunnel output.";
       };
 
       fipsMode = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable FIPS 140-2 mode required for compliance.";
+        description = lib.mdDoc "Enable FIPS 140-2 mode required for compliance.";
       };
 
       enableInsecureSSLv3 = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable support for the insecure SSLv3 protocol.";
+        description = lib.mdDoc "Enable support for the insecure SSLv3 protocol.";
       };
 
 
       servers = mkOption {
-        description = "Define the server configuations.";
-        type = with types; attrsOf (submodule serverConfig);
+        description = lib.mdDoc ''
+          Define the server configurations.
+
+          See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`.
+        '';
+        type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
         example = {
           fancyWebserver = {
             accept = 443;
@@ -143,8 +94,32 @@ in
       };
 
       clients = mkOption {
-        description = "Define the client configurations.";
-        type = with types; attrsOf (submodule clientConfig);
+        description = lib.mdDoc ''
+          Define the client configurations.
+
+          By default, verifyChain and OCSPaia are enabled and a CAFile is provided from pkgs.cacert.
+
+          See "SERVICE-LEVEL OPTIONS" in {manpage}`stunnel(8)`.
+        '';
+        type = with types; attrsOf (attrsOf (nullOr (oneOf [bool int str])));
+
+        apply = let
+          applyDefaults = c:
+            {
+              CAFile = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+              OCSPaia = true;
+              verifyChain = true;
+            } // c;
+          setCheckHostFromVerifyHostname = c:
+            # To preserve backward-compatibility with the old NixOS stunnel module
+            # definition, allow "verifyHostname" as an alias for "checkHost".
+            c // {
+              checkHost = c.checkHost or c.verifyHostname or null;
+              verifyHostname = null; # Not a real stunnel configuration setting
+            };
+          forceClient = c: c // { client = true; };
+        in mapAttrs (_: c: forceClient (setCheckHostFromVerifyHostname (applyDefaults c)));
+
         example = {
           foobar = {
             accept = "0.0.0.0:8080";
@@ -169,6 +144,11 @@ in
       })
 
       (mapAttrsToList verifyChainPathAssert cfg.clients)
+      (mapAttrsToList (verifyRequiredField "client" "accept") cfg.clients)
+      (mapAttrsToList (verifyRequiredField "client" "connect") cfg.clients)
+      (mapAttrsToList (verifyRequiredField "server" "accept") cfg.servers)
+      (mapAttrsToList (verifyRequiredField "server" "cert") cfg.servers)
+      (mapAttrsToList (verifyRequiredField "server" "connect") cfg.servers)
     ];
 
     environment.systemPackages = [ pkgs.stunnel ];
@@ -183,36 +163,10 @@ in
       ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" }
 
       ; ----- SERVER CONFIGURATIONS -----
-      ${ lib.concatStringsSep "\n"
-           (lib.mapAttrsToList
-             (n: v: ''
-               [${n}]
-               accept = ${toString v.accept}
-               connect = ${toString v.connect}
-               cert = ${v.cert}
-
-             '')
-           cfg.servers)
-      }
+      ${ generateConfig cfg.servers }
 
       ; ----- CLIENT CONFIGURATIONS -----
-      ${ lib.concatStringsSep "\n"
-           (lib.mapAttrsToList
-             (n: v: ''
-               [${n}]
-               client = yes
-               accept = ${v.accept}
-               connect = ${v.connect}
-               verifyChain = ${yesNo v.verifyChain}
-               verifyPeer = ${yesNo v.verifyPeer}
-               ${optionalString (v.CAPath != null) "CApath = ${v.CAPath}"}
-               ${optionalString (v.CAFile != null) "CAFile = ${v.CAFile}"}
-               ${optionalString (v.verifyHostname != null) "checkHost = ${v.verifyHostname}"}
-               OCSPaia = yes
-
-             '')
-           cfg.clients)
-      }
+      ${ generateConfig cfg.clients }
     '';
 
     systemd.services.stunnel = {
diff --git a/nixos/modules/services/networking/supplicant.nix b/nixos/modules/services/networking/supplicant.nix
index 8df450a11c63..13d84736e2c2 100644
--- a/nixos/modules/services/networking/supplicant.nix
+++ b/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}
@@ -74,19 +74,19 @@ in
               type = types.nullOr types.path;
               default = null;
               example = literalExpression "/etc/wpa_supplicant.conf";
-              description = ''
-                External <literal>wpa_supplicant.conf</literal> configuration file.
-                The configuration options defined declaratively within <literal>networking.supplicant</literal> have
-                precedence over options defined in <literal>configFile</literal>.
+              description = lib.mdDoc ''
+                External `wpa_supplicant.conf` configuration file.
+                The configuration options defined declaratively within `networking.supplicant` have
+                precedence over options defined in `configFile`.
               '';
             };
 
             writable = mkOption {
               type = types.bool;
               default = false;
-              description = ''
-                Whether the configuration file at <literal>configFile.path</literal> should be written to by
-                <literal>wpa_supplicant</literal>.
+              description = lib.mdDoc ''
+                Whether the configuration file at `configFile.path` should be written to by
+                `wpa_supplicant`.
               '';
             };
 
@@ -109,12 +109,12 @@ in
               model_name=NixOS_Unstable
               model_number=2015
             '';
-            description = ''
-              Configuration options for <literal>wpa_supplicant.conf</literal>.
-              Options defined here have precedence over options in <literal>configFile</literal>.
-              NOTE: Do not write sensitive data into <literal>extraConf</literal> as it will
-              be world-readable in the <literal>nix-store</literal>. For sensitive information
-              use the <literal>configFile</literal> instead.
+            description = lib.mdDoc ''
+              Configuration options for `wpa_supplicant.conf`.
+              Options defined here have precedence over options in `configFile`.
+              NOTE: Do not write sensitive data into `extraConf` as it will
+              be world-readable in the `nix-store`. For sensitive information
+              use the `configFile` instead.
             '';
           };
 
@@ -123,19 +123,19 @@ in
             default = "";
             example = "-e/run/wpa_supplicant/entropy.bin";
             description =
-              "Command line arguments to add when executing <literal>wpa_supplicant</literal>.";
+              lib.mdDoc "Command line arguments to add when executing `wpa_supplicant`.";
           };
 
           driver = mkOption {
             type = types.nullOr types.str;
             default = "nl80211,wext";
-            description = "Force a specific wpa_supplicant driver.";
+            description = lib.mdDoc "Force a specific wpa_supplicant driver.";
           };
 
           bridge = mkOption {
             type = types.str;
             default = "";
-            description = "Name of the bridge interface that wpa_supplicant should listen at.";
+            description = lib.mdDoc "Name of the bridge interface that wpa_supplicant should listen at.";
           };
 
           userControlled = {
@@ -143,7 +143,7 @@ in
             enable = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
                 This is useful for laptop users that switch networks a lot and don't want
                 to depend on a large package such as NetworkManager just to pick nearby
@@ -154,14 +154,14 @@ in
             socketDir = mkOption {
               type = types.str;
               default = "/run/wpa_supplicant";
-              description = "Directory of sockets for controlling wpa_supplicant.";
+              description = lib.mdDoc "Directory of sockets for controlling wpa_supplicant.";
             };
 
             group = mkOption {
               type = types.str;
               default = "wheel";
               example = "network";
-              description = "Members of this group can control wpa_supplicant.";
+              description = lib.mdDoc "Members of this group can control wpa_supplicant.";
             };
 
           };
@@ -184,21 +184,21 @@ in
         }
       '';
 
-      description = ''
-        Interfaces for which to start <command>wpa_supplicant</command>.
+      description = lib.mdDoc ''
+        Interfaces for which to start {command}`wpa_supplicant`.
         The supplicant is used to scan for and associate with wireless networks,
         or to authenticate with 802.1x capable network switches.
 
         The value of this option is an attribute set. Each attribute configures a
-        <command>wpa_supplicant</command> service, where the attribute name specifies
-        the name of the interface that <command>wpa_supplicant</command> operates on.
+        {command}`wpa_supplicant` service, where the attribute name specifies
+        the name of the interface that {command}`wpa_supplicant` operates on.
         The attribute name can be a space separated list of interfaces.
-        The attribute names <literal>WLAN</literal>, <literal>LAN</literal> and <literal>DBUS</literal>
-        have a special meaning. <literal>WLAN</literal> and <literal>LAN</literal> are
-        configurations for universal <command>wpa_supplicant</command> service that is
+        The attribute names `WLAN`, `LAN` and `DBUS`
+        have a special meaning. `WLAN` and `LAN` are
+        configurations for universal {command}`wpa_supplicant` service that is
         started for each WLAN interface or for each LAN interface, respectively.
-        <literal>DBUS</literal> defines a device-unrelated <command>wpa_supplicant</command>
-        service that can be accessed through <literal>D-Bus</literal>.
+        `DBUS` defines a device-unrelated {command}`wpa_supplicant`
+        service that can be accessed through `D-Bus`.
       '';
 
     };
@@ -223,13 +223,13 @@ 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="${pkgs.systemd}/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-wlan@$result.service"
+            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"
           ''}
           ${optionalString (hasAttr "LAN" cfg) ''
-            ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="lan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="${pkgs.systemd}/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-lan@$result.service"
+            ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="lan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="/run/current-system/systemd/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-lan@$result.service"
           ''}
         '';
       })];
diff --git a/nixos/modules/services/networking/supybot.nix b/nixos/modules/services/networking/supybot.nix
index 94b79c7e247f..22ba015cc55d 100644
--- a/nixos/modules/services/networking/supybot.nix
+++ b/nixos/modules/services/networking/supybot.nix
@@ -16,7 +16,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable Supybot, an IRC bot (also known as Limnoria).";
+        description = lib.mdDoc "Enable Supybot, an IRC bot (also known as Limnoria).";
       };
 
       stateDir = mkOption {
@@ -25,12 +25,12 @@ in
           then "/var/lib/supybot"
           else "/home/supybot";
         defaultText = literalExpression "/var/lib/supybot";
-        description = "The root directory, logs and plugins are stored here";
+        description = lib.mdDoc "The root directory, logs and plugins are stored here";
       };
 
       configFile = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to initial supybot config file. This can be generated by
           running supybot-wizard.
 
@@ -42,12 +42,12 @@ in
       plugins = mkOption {
         type = types.attrsOf types.path;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Attribute set of additional plugins that will be symlinked to the
-          <filename>plugin</filename> subdirectory.
+          {file}`plugin` subdirectory.
 
           Please note that you still need to add the plugins to the config
-          file (or with <literal>!load</literal>) using their attribute name.
+          file (or with `!load`) using their attribute name.
         '';
         example = literalExpression ''
           let
@@ -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/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix
index 7694b4bf990b..726f65671072 100644
--- a/nixos/modules/services/networking/syncplay.nix
+++ b/nixos/modules/services/networking/syncplay.nix
@@ -17,13 +17,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "If enabled, start the Syncplay server.";
+        description = lib.mdDoc "If enabled, start the Syncplay server.";
       };
 
       port = mkOption {
         type = types.port;
         default = 8999;
-        description = ''
+        description = lib.mdDoc ''
           TCP port to bind to.
         '';
       };
@@ -31,7 +31,7 @@ in
       salt = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Salt to allow room operator passwords generated by this server
           instance to still work when the server is restarted.
         '';
@@ -40,16 +40,16 @@ in
       certDir = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           TLS certificates directory to use for encryption. See
-          <link xlink:href="https://github.com/Syncplay/syncplay/wiki/TLS-support"/>.
+          <https://github.com/Syncplay/syncplay/wiki/TLS-support>.
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "nobody";
-        description = ''
+        description = lib.mdDoc ''
           User to use when running Syncplay.
         '';
       };
@@ -57,7 +57,7 @@ in
       group = mkOption {
         type = types.str;
         default = "nogroup";
-        description = ''
+        description = lib.mdDoc ''
           Group to use when running Syncplay.
         '';
       };
@@ -65,9 +65,9 @@ in
       passwordFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to the file that contains the server password. If
-          <literal>null</literal>, the server doesn't require a password.
+          `null`, the server doesn't require a password.
         '';
       };
     };
diff --git a/nixos/modules/services/networking/syncthing-relay.nix b/nixos/modules/services/networking/syncthing-relay.nix
index f5ca63e78930..64c4e731b982 100644
--- a/nixos/modules/services/networking/syncthing-relay.nix
+++ b/nixos/modules/services/networking/syncthing-relay.nix
@@ -22,13 +22,13 @@ in {
   ###### interface
 
   options.services.syncthing.relay = {
-    enable = mkEnableOption "Syncthing relay service";
+    enable = mkEnableOption (lib.mdDoc "Syncthing relay service");
 
     listenAddress = mkOption {
       type = types.str;
       default = "";
       example = "1.2.3.4";
-      description = ''
+      description = lib.mdDoc ''
         Address to listen on for relay traffic.
       '';
     };
@@ -36,9 +36,9 @@ in {
     port = mkOption {
       type = types.port;
       default = 22067;
-      description = ''
+      description = lib.mdDoc ''
         Port to listen on for relay traffic. This port should be added to
-        <literal>networking.firewall.allowedTCPPorts</literal>.
+        `networking.firewall.allowedTCPPorts`.
       '';
     };
 
@@ -46,7 +46,7 @@ in {
       type = types.str;
       default = "";
       example = "1.2.3.4";
-      description = ''
+      description = lib.mdDoc ''
         Address to listen on for serving the relay status API.
       '';
     };
@@ -54,16 +54,16 @@ in {
     statusPort = mkOption {
       type = types.port;
       default = 22070;
-      description = ''
+      description = lib.mdDoc ''
         Port to listen on for serving the relay status API. This port should be
-        added to <literal>networking.firewall.allowedTCPPorts</literal>.
+        added to `networking.firewall.allowedTCPPorts`.
       '';
     };
 
     pools = mkOption {
       type = types.nullOr (types.listOf types.str);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Relay pools to join. If null, uses the default global pool.
       '';
     };
@@ -71,7 +71,7 @@ in {
     providedBy = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Human-readable description of the provider of the relay (you).
       '';
     };
@@ -79,7 +79,7 @@ in {
     globalRateBps = mkOption {
       type = types.nullOr types.ints.positive;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Global bandwidth rate limit in bytes per second.
       '';
     };
@@ -87,7 +87,7 @@ in {
     perSessionRateBps = mkOption {
       type = types.nullOr types.ints.positive;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Per session bandwidth rate limit in bytes per second.
       '';
     };
@@ -95,7 +95,7 @@ in {
     extraOptions = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Extra command line arguments to pass to strelaysrv.
       '';
     };
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 3a3d4c80ecff..adbb25ccb9b6 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -30,15 +30,22 @@ let
   updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
     set -efu
 
+    # be careful not to leak secrets in the filesystem or in process listings
+
+    umask 0077
+
     # get the api key by parsing the config.xml
     while
-        ! api_key=$(${pkgs.libxml2}/bin/xmllint \
+        ! ${pkgs.libxml2}/bin/xmllint \
             --xpath 'string(configuration/gui/apikey)' \
-            ${cfg.configDir}/config.xml)
+            ${cfg.configDir}/config.xml \
+            >"$RUNTIME_DIRECTORY/api_key"
     do sleep 1; done
 
+    (printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
+
     curl() {
-        ${pkgs.curl}/bin/curl -sSLk -H "X-API-Key: $api_key" \
+        ${pkgs.curl}/bin/curl -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
             --retry 1000 --retry-delay 1 --retry-all-errors \
             "$@"
     }
@@ -48,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
@@ -67,44 +74,44 @@ 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;
         default = null;
-        description = ''
-          Path to the <literal>cert.pem</literal> file, which will be copied into Syncthing's
-          <link linkend="opt-services.syncthing.configDir">configDir</link>.
+        description = mdDoc ''
+          Path to the `cert.pem` file, which will be copied into Syncthing's
+          [configDir](#opt-services.syncthing.configDir).
         '';
       };
 
       key = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
-          Path to the <literal>key.pem</literal> file, which will be copied into Syncthing's
-          <link linkend="opt-services.syncthing.configDir">configDir</link>.
+        description = mdDoc ''
+          Path to the `key.pem` file, which will be copied into Syncthing's
+          [configDir](#opt-services.syncthing.configDir).
         '';
       };
 
       overrideDevices = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = mdDoc ''
           Whether to delete the devices which are not configured via the
-          <link linkend="opt-services.syncthing.devices">devices</link> option.
-          If set to <literal>false</literal>, devices added via the web
+          [devices](#opt-services.syncthing.devices) option.
+          If set to `false`, devices added via the web
           interface will persist and will have to be deleted manually.
         '';
       };
 
       devices = mkOption {
         default = {};
-        description = ''
+        description = mdDoc ''
           Peers/devices which Syncthing should communicate with.
 
           Note that you can still add devices manually, but those changes
-          will be reverted on restart if <link linkend="opt-services.syncthing.overrideDevices">overrideDevices</link>
+          will be reverted on restart if [overrideDevices](#opt-services.syncthing.overrideDevices)
           is enabled.
         '';
         example = {
@@ -119,7 +126,7 @@ in {
             name = mkOption {
               type = types.str;
               default = name;
-              description = ''
+              description = lib.mdDoc ''
                 The name of the device.
               '';
             };
@@ -127,7 +134,7 @@ in {
             addresses = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = ''
+              description = lib.mdDoc ''
                 The addresses used to connect to the device.
                 If this is left empty, dynamic configuration is attempted.
               '';
@@ -135,27 +142,27 @@ in {
 
             id = mkOption {
               type = types.str;
-              description = ''
-                The device ID. See <link xlink:href="https://docs.syncthing.net/dev/device-ids.html"/>.
+              description = mdDoc ''
+                The device ID. See <https://docs.syncthing.net/dev/device-ids.html>.
               '';
             };
 
             introducer = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = mdDoc ''
                 Whether the device should act as an introducer and be allowed
                 to add folders on this computer.
-                See <link xlink:href="https://docs.syncthing.net/users/introducer.html"/>.
+                See <https://docs.syncthing.net/users/introducer.html>.
               '';
             };
 
             autoAcceptFolders = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = mdDoc ''
                 Automatically create or share folders that this device advertises at the default path.
-                See <link xlink:href="https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format"/>.
+                See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>.
               '';
             };
 
@@ -166,21 +173,21 @@ in {
       overrideFolders = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = mdDoc ''
           Whether to delete the folders which are not configured via the
-          <link linkend="opt-services.syncthing.folders">folders</link> option.
-          If set to <literal>false</literal>, folders added via the web
+          [folders](#opt-services.syncthing.folders) option.
+          If set to `false`, folders added via the web
           interface will persist and will have to be deleted manually.
         '';
       };
 
       folders = mkOption {
         default = {};
-        description = ''
+        description = mdDoc ''
           Folders which should be shared by Syncthing.
 
-          Note that you can still add devices manually, but those changes
-          will be reverted on restart if <link linkend="opt-services.syncthing.overrideDevices">overrideDevices</link>
+          Note that you can still add folders manually, but those changes
+          will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders)
           is enabled.
         '';
         example = literalExpression ''
@@ -197,7 +204,7 @@ in {
             enable = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to share this folder.
                 This option is useful when you want to define all folders
                 in one place, but not every machine should share all folders.
@@ -205,17 +212,25 @@ 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 = ''
+              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.
               '';
             };
 
             id = mkOption {
               type = types.str;
               default = name;
-              description = ''
+              description = lib.mdDoc ''
                 The ID of the folder. Must be the same on all devices.
               '';
             };
@@ -223,7 +238,7 @@ in {
             label = mkOption {
               type = types.str;
               default = name;
-              description = ''
+              description = lib.mdDoc ''
                 The label of the folder.
               '';
             };
@@ -231,18 +246,18 @@ in {
             devices = mkOption {
               type = types.listOf types.str;
               default = [];
-              description = ''
+              description = mdDoc ''
                 The devices this folder should be shared with. Each device must
-                be defined in the <link linkend="opt-services.syncthing.devices">devices</link> option.
+                be defined in the [devices](#opt-services.syncthing.devices) option.
               '';
             };
 
             versioning = mkOption {
               default = null;
-              description = ''
+              description = mdDoc ''
                 How to keep changed/deleted files with Syncthing.
                 There are 4 different types of versioning with different parameters.
-                See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>.
+                See <https://docs.syncthing.net/users/versioning.html>.
               '';
               example = literalExpression ''
                 [
@@ -261,10 +276,10 @@ in {
                   {
                     versioning = {
                       type = "staggered";
+                      fsPath = "/syncthing/backup";
                       params = {
                         cleanInterval = "3600";
                         maxAge = "31536000";
-                        versionsPath = "/syncthing/backup";
                       };
                     };
                   }
@@ -284,17 +299,25 @@ in {
                 options = {
                   type = mkOption {
                     type = enum [ "external" "simple" "staggered" "trashcan" ];
-                    description = ''
+                    description = mdDoc ''
                       The type of versioning.
-                      See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>.
+                      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 = ''
+                    description = mdDoc ''
                       The parameters for versioning. Structure depends on
-                      <link linkend="opt-services.syncthing.folders._name_.versioning.type">versioning.type</link>.
-                      See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>.
+                      [versioning.type](#opt-services.syncthing.folders._name_.versioning.type).
+                      See <https://docs.syncthing.net/users/versioning.html>.
                     '';
                   };
                 };
@@ -304,24 +327,25 @@ in {
             rescanInterval = mkOption {
               type = types.int;
               default = 3600;
-              description = ''
+              description = lib.mdDoc ''
                 How often the folder should be rescanned for changes.
               '';
             };
 
             type = mkOption {
-              type = types.enum [ "sendreceive" "sendonly" "receiveonly" ];
+              type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ];
               default = "sendreceive";
-              description = ''
+              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.
               '';
             };
 
             watch = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether the folder should be watched for changes by inotify.
               '';
             };
@@ -329,7 +353,7 @@ in {
             watchDelay = mkOption {
               type = types.int;
               default = 10;
-              description = ''
+              description = lib.mdDoc ''
                 The delay after an inotify event is triggered.
               '';
             };
@@ -337,7 +361,7 @@ in {
             ignorePerms = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to ignore permission changes.
               '';
             };
@@ -345,9 +369,9 @@ in {
             ignoreDelete = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = mdDoc ''
                 Whether to skip deleting files that are deleted by peers.
-                See <link xlink:href="https://docs.syncthing.net/advanced/folder-ignoredelete.html"/>.
+                See <https://docs.syncthing.net/advanced/folder-ignoredelete.html>.
               '';
             };
           };
@@ -357,9 +381,9 @@ in {
       extraOptions = mkOption {
         type = types.addCheck (pkgs.formats.json {}).type isAttrs;
         default = {};
-        description = ''
+        description = mdDoc ''
           Extra configuration options for Syncthing.
-          See <link xlink:href="https://docs.syncthing.net/users/config.html"/>.
+          See <https://docs.syncthing.net/users/config.html>.
         '';
         example = {
           options.localAnnounceEnabled = false;
@@ -370,7 +394,7 @@ in {
       guiAddress = mkOption {
         type = types.str;
         default = "127.0.0.1:8384";
-        description = ''
+        description = lib.mdDoc ''
           The address to serve the web interface at.
         '';
       };
@@ -378,7 +402,7 @@ in {
       systemService = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to auto-launch Syncthing as a system service.
         '';
       };
@@ -387,9 +411,10 @@ in {
         type = types.str;
         default = defaultUser;
         example = "yourUser";
-        description = ''
+        description = mdDoc ''
           The user to run Syncthing as.
-          By default, a user named <literal>${defaultUser}</literal> will be created.
+          By default, a user named `${defaultUser}` will be created whose home
+          directory is [dataDir](#opt-services.syncthing.dataDir).
         '';
       };
 
@@ -397,9 +422,9 @@ in {
         type = types.str;
         default = defaultGroup;
         example = "yourGroup";
-        description = ''
+        description = mdDoc ''
           The group to run Syncthing under.
-          By default, a group named <literal>${defaultGroup}</literal> will be created.
+          By default, a group named `${defaultGroup}` will be created.
         '';
       };
 
@@ -407,11 +432,11 @@ in {
         type = with types; nullOr str;
         default = null;
         example = "socks5://address.com:1234";
-        description = ''
+        description = mdDoc ''
           Overwrites the all_proxy environment variable for the Syncthing process to
           the given value. This is normally used to let Syncthing connect
           through a SOCKS5 proxy server.
-          See <link xlink:href="https://docs.syncthing.net/users/proxying.html"/>.
+          See <https://docs.syncthing.net/users/proxying.html>.
         '';
       };
 
@@ -419,7 +444,7 @@ in {
         type = types.path;
         default = "/var/lib/syncthing";
         example = "/home/yourUser";
-        description = ''
+        description = lib.mdDoc ''
           The path where synchronised directories will exist.
         '';
       };
@@ -428,29 +453,17 @@ in {
         cond = versionAtLeast config.system.stateVersion "19.03";
       in mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path where the settings and keys will exist.
         '';
         default = cfg.dataDir + optionalString cond "/.config/syncthing";
-        defaultText = literalDocBook ''
-          <variablelist>
-            <varlistentry>
-              <term><literal>stateVersion >= 19.03</literal></term>
-              <listitem>
-                <programlisting>
-                  config.${opt.dataDir} + "/.config/syncthing"
-                </programlisting>
-              </listitem>
-            </varlistentry>
-            <varlistentry>
-              <term>otherwise</term>
-              <listitem>
-                <programlisting>
-                  config.${opt.dataDir}
-                </programlisting>
-              </listitem>
-            </varlistentry>
-          </variablelist>
+        defaultText = literalMD ''
+          * if `stateVersion >= 19.03`:
+
+                config.${opt.dataDir} + "/.config/syncthing"
+          * otherwise:
+
+                config.${opt.dataDir}
         '';
       };
 
@@ -458,7 +471,7 @@ in {
         type = types.listOf types.str;
         default = [];
         example = [ "--reset-deltas" ];
-        description = ''
+        description = lib.mdDoc ''
           Extra flags passed to the syncthing command in the service definition.
         '';
       };
@@ -467,7 +480,7 @@ in {
         type = types.bool;
         default = false;
         example = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to open the default ports in the firewall: TCP/UDP 22000 for transfers
           and UDP 21027 for discovery.
 
@@ -482,7 +495,7 @@ in {
         type = types.package;
         default = pkgs.syncthing;
         defaultText = literalExpression "pkgs.syncthing";
-        description = ''
+        description = lib.mdDoc ''
           The Syncthing package to use.
         '';
       };
@@ -525,6 +538,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" ];
@@ -536,7 +551,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;
@@ -588,6 +603,7 @@ in {
         serviceConfig = {
           User = cfg.user;
           RemainAfterExit = true;
+          RuntimeDirectory = "syncthing-init";
           Type = "oneshot";
           ExecStart = updateConfig;
         };
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index 1f64113950a7..233bfdf9ebf5 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -2,36 +2,52 @@
 
 with lib;
 
-let cfg = config.services.tailscale;
+let
+  cfg = config.services.tailscale;
+  isNetworkd = config.networking.useNetworkd;
 in {
-  meta.maintainers = with maintainers; [ danderson mbaillie ];
+  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;
       default = 41641;
-      description = "The port to listen on for tunnel traffic (0=autoselect).";
+      description = lib.mdDoc "The port to listen on for tunnel traffic (0=autoselect).";
     };
 
     interfaceName = mkOption {
       type = types.str;
       default = "tailscale0";
-      description = ''The interface name for tunnel traffic. Use "userspace-networking" (beta) to not use TUN.'';
+      description = lib.mdDoc ''The interface name for tunnel traffic. Use "userspace-networking" (beta) to not use TUN.'';
     };
 
     permitCertUid = mkOption {
       type = types.nullOr types.nonEmptyStr;
       default = null;
-      description = "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node.";
+      description = lib.mdDoc "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node.";
     };
 
     package = mkOption {
       type = types.package;
       default = pkgs.tailscale;
       defaultText = literalExpression "pkgs.tailscale";
-      description = "The package to use for 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.
+      '';
     };
   };
 
@@ -40,13 +56,48 @@ in {
     systemd.packages = [ cfg.package ];
     systemd.services.tailscaled = {
       wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.openresolv pkgs.procps ];
+      path = [
+        config.networking.resolvconf.package # for configuring DNS in some configs
+        pkgs.procps     # for collecting running services (opt-in feature)
+        pkgs.glibc      # for `getent` to look up user shells
+      ];
       serviceConfig.Environment = [
         "PORT=${toString cfg.port}"
         ''"FLAGS=--tun ${lib.escapeShellArg cfg.interfaceName}"''
       ] ++ (lib.optionals (cfg.permitCertUid != null) [
         "TS_PERMIT_CERT_UID=${cfg.permitCertUid}"
       ]);
+      # Restart tailscaled with a single `systemctl restart` at the
+      # end of activation, rather than a `stop` followed by a later
+      # `start`. Activation over Tailscale can hang for tens of
+      # seconds in the stop+start setup, if the activation script has
+      # a significant delay between the stop and start phases
+      # (e.g. script blocked on another unit with a slow shutdown).
+      #
+      # Tailscale is aware of the correctness tradeoff involved, and
+      # already makes its upstream systemd unit robust against unit
+      # version mismatches on restart for compatibility with other
+      # linux distros.
+      stopIfChanged = false;
+    };
+
+    boot.kernel.sysctl = mkIf (cfg.useRoutingFeatures == "server" || cfg.useRoutingFeatures == "both") {
+      "net.ipv4.conf.all.forwarding" = mkDefault true;
+      "net.ipv6.conf.all.forwarding" = mkDefault true;
+    };
+
+    networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose";
+
+    networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ];
+
+    systemd.network.networks."50-tailscale" = mkIf isNetworkd {
+      matchConfig = {
+        Name = cfg.interfaceName;
+      };
+      linkConfig = {
+        Unmanaged = true;
+        ActivationPolicy = "manual";
+      };
     };
   };
 }
diff --git a/nixos/modules/services/networking/tayga.nix b/nixos/modules/services/networking/tayga.nix
new file mode 100644
index 000000000000..299ae2777f7c
--- /dev/null
+++ b/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/nixos/modules/services/networking/tcpcrypt.nix b/nixos/modules/services/networking/tcpcrypt.nix
index 5a91054e1668..f2115a6660cb 100644
--- a/nixos/modules/services/networking/tcpcrypt.nix
+++ b/nixos/modules/services/networking/tcpcrypt.nix
@@ -17,7 +17,7 @@ in
     networking.tcpcrypt.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable opportunistic TCP encryption. If the other end
         speaks Tcpcrypt, then your traffic will be encrypted; otherwise
         it will be sent in clear text. Thus, Tcpcrypt alone provides no
diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix
index c0ed08282aaf..f09ef1a959ed 100644
--- a/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixos/modules/services/networking/teamspeak3.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run the Teamspeak3 voice communication server daemon.
         '';
       };
@@ -27,7 +27,7 @@ in
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/teamspeak3-server";
-        description = ''
+        description = lib.mdDoc ''
           Directory to store TS3 database and other state/data files.
         '';
       };
@@ -35,7 +35,7 @@ in
       logPath = mkOption {
         type = types.path;
         default = "/var/log/teamspeak3-server/";
-        description = ''
+        description = lib.mdDoc ''
           Directory to store log files in.
         '';
       };
@@ -44,7 +44,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "[::]";
-        description = ''
+        description = lib.mdDoc ''
           IP on which the server instance will listen for incoming voice connections. Defaults to any IP.
         '';
       };
@@ -52,7 +52,7 @@ in
       defaultVoicePort = mkOption {
         type = types.int;
         default = 9987;
-        description = ''
+        description = lib.mdDoc ''
           Default UDP port for clients to connect to virtual servers - used for first virtual server, subsequent ones will open on incrementing port numbers by default.
         '';
       };
@@ -61,7 +61,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "[::]";
-        description = ''
+        description = lib.mdDoc ''
           IP on which the server instance will listen for incoming file transfer connections. Defaults to any IP.
         '';
       };
@@ -69,7 +69,7 @@ in
       fileTransferPort = mkOption {
         type = types.int;
         default = 30033;
-        description = ''
+        description = lib.mdDoc ''
           TCP port opened for file transfers.
         '';
       };
@@ -78,7 +78,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "0.0.0.0";
-        description = ''
+        description = lib.mdDoc ''
           IP on which the server instance will listen for incoming ServerQuery connections. Defaults to any IP.
         '';
       };
@@ -86,7 +86,7 @@ in
       queryPort = mkOption {
         type = types.int;
         default = 10011;
-        description = ''
+        description = lib.mdDoc ''
           TCP port opened for ServerQuery connections.
         '';
       };
@@ -94,13 +94,13 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the TeamSpeak3 server.";
+        description = lib.mdDoc "Open ports in the firewall for the TeamSpeak3 server.";
       };
 
       openFirewallServerQuery = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the TeamSpeak3 serverquery (administration) system. Requires openFirewall.";
+        description = lib.mdDoc "Open ports in the firewall for the TeamSpeak3 serverquery (administration) system. Requires openFirewall.";
       };
 
     };
@@ -152,6 +152,7 @@ in
         WorkingDirectory = cfg.dataDir;
         User = user;
         Group = group;
+        Restart = "on-failure";
       };
     };
   };
diff --git a/nixos/modules/services/networking/tedicross.nix b/nixos/modules/services/networking/tedicross.nix
index c7830289dca0..cee7e11f4fb1 100644
--- a/nixos/modules/services/networking/tedicross.nix
+++ b/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;
@@ -57,9 +57,9 @@ in {
             debug = false;
           }
         '';
-        description = ''
-          <filename>settings.yaml</filename> configuration as a Nix attribute set.
-          Secret tokens should be specified using <option>environmentFile</option>
+        description = lib.mdDoc ''
+          {file}`settings.yaml` configuration as a Nix attribute set.
+          Secret tokens should be specified using {option}`environmentFile`
           instead of this world-readable file.
         '';
       };
@@ -67,10 +67,10 @@ in {
       environmentFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           File containing environment variables to be passed to the TediCross service,
           in which secret tokens can be specified securely using the
-          <literal>TELEGRAM_BOT_TOKEN</literal> and <literal>DISCORD_BOT_TOKEN</literal>
+          `TELEGRAM_BOT_TOKEN` and `DISCORD_BOT_TOKEN`
           keys.
         '';
       };
diff --git a/nixos/modules/services/networking/teleport.nix b/nixos/modules/services/networking/teleport.nix
index 454791621800..6433554f87da 100644
--- a/nixos/modules/services/networking/teleport.nix
+++ b/nixos/modules/services/networking/teleport.nix
@@ -9,7 +9,7 @@ in
 {
   options = {
     services.teleport = with lib.types; {
-      enable = mkEnableOption "the Teleport service";
+      enable = mkEnableOption (lib.mdDoc "the Teleport service");
 
       settings = mkOption {
         type = settingsYaml.type;
@@ -33,15 +33,15 @@ in
             auth_service.enabled = false;
           }
         '';
-        description = ''
-          Contents of the <literal>teleport.yaml</literal> config file.
-          The <literal>--config</literal> arguments will only be passed if this set is not empty.
+        description = lib.mdDoc ''
+          Contents of the `teleport.yaml` config file.
+          The `--config` arguments will only be passed if this set is not empty.
 
-          See <link xlink:href="https://goteleport.com/docs/setup/reference/config/"/>.
+          See <https://goteleport.com/docs/setup/reference/config/>.
         '';
       };
 
-      insecure.enable = mkEnableOption ''
+      insecure.enable = mkEnableOption (lib.mdDoc ''
         starting teleport in insecure mode.
 
         This is dangerous!
@@ -49,25 +49,25 @@ 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;
           default = "127.0.0.1";
-          description = "Metrics and diagnostics address.";
+          description = lib.mdDoc "Metrics and diagnostics address.";
         };
 
         port = mkOption {
-          type = int;
+          type = port;
           default = 3000;
-          description = "Metrics and diagnostics port.";
+          description = lib.mdDoc "Metrics and diagnostics port.";
         };
       };
     };
diff --git a/nixos/modules/services/networking/tetrd.nix b/nixos/modules/services/networking/tetrd.nix
index 0801ce129246..6284a5b1fb1b 100644
--- a/nixos/modules/services/networking/tetrd.nix
+++ b/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/nixos/modules/services/networking/tftpd.nix b/nixos/modules/services/networking/tftpd.nix
index c9c0a2b321d5..a4dc137daa4c 100644
--- a/nixos/modules/services/networking/tftpd.nix
+++ b/nixos/modules/services/networking/tftpd.nix
@@ -11,7 +11,7 @@ with lib;
     services.tftpd.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable tftpd, a Trivial File Transfer Protocol server.
         The server will be run as an xinetd service.
       '';
@@ -20,7 +20,7 @@ with lib;
     services.tftpd.path = mkOption {
       type = types.path;
       default = "/srv/tftp";
-      description = ''
+      description = lib.mdDoc ''
         Where the tftp server files are stored.
       '';
     };
diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
index a5118fd8b339..a188ffe866b5 100644
--- a/nixos/modules/services/networking/thelounge.nix
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -23,16 +23,16 @@ 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");
 
     public = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Make your The Lounge instance public.
-        Setting this to <literal>false</literal> will require you to configure user
-        accounts by using the (<command>thelounge</command>) command or by adding
-        entries in <filename>${dataDir}/users</filename>. You might need to restart
+        Setting this to `false` will require you to configure user
+        accounts by using the ({command}`thelounge`) command or by adding
+        entries in {file}`${dataDir}/users`. You might need to restart
         The Lounge after making changes to the state directory.
       '';
     };
@@ -40,7 +40,7 @@ in
     port = mkOption {
       type = types.port;
       default = 9000;
-      description = "TCP port to listen on for http connections.";
+      description = lib.mdDoc "TCP port to listen on for http connections.";
     };
 
     extraConfig = mkOption {
@@ -54,14 +54,14 @@ in
           port = 6697;
         };
       }'';
-      description = ''
-        The Lounge's <filename>config.js</filename> contents as attribute set (will be
+      description = lib.mdDoc ''
+        The Lounge's {file}`config.js` contents as attribute set (will be
         converted to JSON to generate the configuration file).
 
         The options defined here will be merged to the default configuration file.
-        Note: In case of duplicate configuration, options from <option>extraConfig</option> have priority.
+        Note: In case of duplicate configuration, options from {option}`extraConfig` have priority.
 
-        Documentation: <link xlink:href="https://thelounge.chat/docs/server/configuration" />
+        Documentation: <https://thelounge.chat/docs/server/configuration>
       '';
     };
 
@@ -69,9 +69,9 @@ in
       default = [ ];
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.theLoungePlugins.themes.solarized ]";
-      description = ''
+      description = lib.mdDoc ''
         The Lounge plugins to install. Plugins can be found in
-        <literal>pkgs.theLoungePlugins.plugins</literal> and <literal>pkgs.theLoungePlugins.themes</literal>.
+        `pkgs.theLoungePlugins.plugins` and `pkgs.theLoungePlugins.themes`.
       '';
     };
   };
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 31731b60d484..09b23a60a4af 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -24,13 +24,13 @@ let
     options = {
       address = mkOption {
         type = types.str;
-        description = "The external IP address or hostname where the host can be reached.";
+        description = lib.mdDoc "The external IP address or hostname where the host can be reached.";
       };
 
       port = mkOption {
         type = types.nullOr types.port;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The port where the host can be reached.
 
           If no port is specified, the default Port is used.
@@ -43,7 +43,7 @@ let
     options = {
       address = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The subnet of this host.
 
           Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case
@@ -60,7 +60,7 @@ let
       prefixLength = mkOption {
         type = with types; nullOr (addCheck int (n: n >= 0 && n <= 128));
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The prefix length of the subnet.
 
           If null, a subnet consisting of only that single address is assumed.
@@ -72,7 +72,7 @@ let
       weight = mkOption {
         type = types.ints.unsigned;
         default = 10;
-        description = ''
+        description = lib.mdDoc ''
           Indicates the priority over identical Subnets owned by different nodes.
 
           Lower values indicate higher priority. Packets will be sent to the
@@ -89,9 +89,9 @@ let
       addresses = mkOption {
         type = types.listOf (types.submodule addressSubmodule);
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           The external address where the host can be reached. This will set this
-          host's <option>settings.Address</option> option.
+          host's {option}`settings.Address` option.
 
           This variable is only required if you want to connect to this host.
         '';
@@ -100,9 +100,9 @@ let
       subnets = mkOption {
         type = types.listOf (types.submodule subnetSubmodule);
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           The subnets which this tinc daemon will serve. This will set this
-          host's <option>settings.Subnet</option> option.
+          host's {option}`settings.Subnet` option.
 
           Tinc tries to look up which other daemon it should send a packet to by
           searching the appropriate subnet. If the packet matches a subnet, it
@@ -114,24 +114,24 @@ let
       rsaPublicKey = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Legacy RSA public key of the host in PEM format, including start and
           end markers.
 
           This will be appended as-is in the host's configuration file.
 
           The ed25519 public key can be specified using the
-          <option>settings.Ed25519PublicKey</option> option instead.
+          {option}`settings.Ed25519PublicKey` option instead.
         '';
       };
 
       settings = mkOption {
         default = { };
         type = types.submodule { freeformType = tincConfType; };
-        description = ''
+        description = lib.mdDoc ''
           Configuration for this host.
 
-          See <link xlink:href="https://tinc-vpn.org/documentation-1.1/Host-configuration-variables.html"/>
+          See <https://tinc-vpn.org/documentation-1.1/Host-configuration-variables.html>
           for supported values.
         '';
       };
@@ -167,10 +167,10 @@ in
             extraConfig = mkOption {
               default = "";
               type = types.lines;
-              description = ''
+              description = lib.mdDoc ''
                 Extra lines to add to the tinc service configuration file.
 
-                Note that using the declarative <option>service.tinc.networks.&lt;name&gt;.settings</option>
+                Note that using the declarative {option}`service.tinc.networks.<name>.settings`
                 option is preferred.
               '';
             };
@@ -178,7 +178,7 @@ in
             name = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The name of the node which is used as an identifier when communicating
                 with the remote nodes in the mesh. If null then the hostname of the system
                 is used to derive a name (note that tinc may replace non-alphanumeric characters in
@@ -189,7 +189,7 @@ in
             ed25519PrivateKeyFile = mkOption {
               default = null;
               type = types.nullOr types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path of the private ed25519 keyfile.
               '';
             };
@@ -197,7 +197,7 @@ in
             rsaPrivateKeyFile = mkOption {
               default = null;
               type = types.nullOr types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path of the private RSA keyfile.
               '';
             };
@@ -205,9 +205,9 @@ in
             debugLevel = mkOption {
               default = 0;
               type = types.addCheck types.int (l: l >= 0 && l <= 5);
-              description = ''
+              description = lib.mdDoc ''
                 The amount of debugging information to add to the log. 0 means little
-                logging while 5 is the most logging. <command>man tincd</command> for
+                logging while 5 is the most logging. {command}`man tincd` for
                 more details.
               '';
             };
@@ -215,11 +215,11 @@ in
             hosts = mkOption {
               default = { };
               type = types.attrsOf types.lines;
-              description = ''
+              description = lib.mdDoc ''
                 The name of the host in the network as well as the configuration for that host.
                 This name should only contain alphanumerics and underscores.
 
-                Note that using the declarative <option>service.tinc.networks.&lt;name&gt;.hostSettings</option>
+                Note that using the declarative {option}`service.tinc.networks.<name>.hostSettings`
                 option is preferred.
               '';
             };
@@ -249,7 +249,7 @@ in
                 }
               '';
               type = types.attrsOf (types.submodule hostSubmodule);
-              description = ''
+              description = lib.mdDoc ''
                 The name of the host in the network as well as the configuration for that host.
                 This name should only contain alphanumerics and underscores.
               '';
@@ -258,7 +258,7 @@ in
             interfaceType = mkOption {
               default = "tun";
               type = types.enum [ "tun" "tap" ];
-              description = ''
+              description = lib.mdDoc ''
                 The type of virtual interface used for the network connection.
               '';
             };
@@ -266,7 +266,7 @@ in
             listenAddress = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The ip address to listen on for incoming connections.
               '';
             };
@@ -274,7 +274,7 @@ in
             bindToAddress = mkOption {
               default = null;
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The ip address to bind to (both listen on and send packets from).
               '';
             };
@@ -283,7 +283,7 @@ in
               type = types.package;
               default = pkgs.tinc_pre;
               defaultText = literalExpression "pkgs.tinc_pre";
-              description = ''
+              description = lib.mdDoc ''
                 The package to use for the tinc daemon's binary.
               '';
             };
@@ -291,7 +291,7 @@ in
             chroot = mkOption {
               default = false;
               type = types.bool;
-              description = ''
+              description = lib.mdDoc ''
                 Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
                 The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
 
@@ -309,10 +309,10 @@ in
                   Mode = "switch";
                 }
               '';
-              description = ''
+              description = lib.mdDoc ''
                 Configuration of the Tinc daemon for this network.
 
-                See <link xlink:href="https://tinc-vpn.org/documentation-1.1/Main-configuration-variables.html"/>
+                See <https://tinc-vpn.org/documentation-1.1/Main-configuration-variables.html>
                 for supported values.
               '';
             };
@@ -337,7 +337,7 @@ in
           };
         }));
 
-        description = ''
+        description = lib.mdDoc ''
           Defines the tinc networks which will be started.
           Each network invokes a different daemon.
         '';
@@ -410,7 +410,7 @@ in
     environment.systemPackages = let
       cli-wrappers = pkgs.stdenv.mkDerivation {
         name = "tinc-cli-wrappers";
-        buildInputs = [ pkgs.makeWrapper ];
+        nativeBuildInputs = [ pkgs.makeWrapper ];
         buildCommand = ''
           mkdir -p $out/bin
           ${concatStringsSep "\n" (mapAttrsToList (network: data:
diff --git a/nixos/modules/services/networking/tinydns.nix b/nixos/modules/services/networking/tinydns.nix
index 2c44ad49296d..ea91af5f1967 100644
--- a/nixos/modules/services/networking/tinydns.nix
+++ b/nixos/modules/services/networking/tinydns.nix
@@ -10,19 +10,19 @@ with lib;
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to run the tinydns dns server";
+        description = lib.mdDoc "Whether to run the tinydns dns server";
       };
 
       data = mkOption {
         type = types.lines;
         default = "";
-        description = "The DNS data to serve, in the format described by tinydns-data(8)";
+        description = lib.mdDoc "The DNS data to serve, in the format described by tinydns-data(8)";
       };
 
       ip = mkOption {
         default = "0.0.0.0";
         type = types.str;
-        description = "IP address on which to listen for connections";
+        description = lib.mdDoc "IP address on which to listen for connections";
       };
     };
   };
diff --git a/nixos/modules/services/networking/tmate-ssh-server.nix b/nixos/modules/services/networking/tmate-ssh-server.nix
new file mode 100644
index 000000000000..f7740b1ddfcc
--- /dev/null
+++ b/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/nixos/modules/services/networking/tox-bootstrapd.nix b/nixos/modules/services/networking/tox-bootstrapd.nix
index 7c13724e084a..5c7e7a4c2208 100644
--- a/nixos/modules/services/networking/tox-bootstrapd.nix
+++ b/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -23,31 +23,31 @@ in
             type = types.bool;
             default = false;
             description =
-              ''
+              lib.mdDoc ''
                 Whether to enable the Tox DHT bootstrap daemon.
               '';
           };
 
           port = mkOption {
-            type = types.int;
+            type = types.port;
             default = 33445;
-            description = "Listening port (UDP).";
+            description = lib.mdDoc "Listening port (UDP).";
           };
 
           keysFile = mkOption {
             type = types.str;
             default = "${WorkingDirectory}/keys";
-            description = "Node key file.";
+            description = lib.mdDoc "Node key file.";
           };
 
           extraConfig = mkOption {
             type = types.lines;
             default = "";
             description =
-              ''
+              lib.mdDoc ''
                 Configuration for bootstrap daemon.
-                See <link xlink:href="https://github.com/irungentoo/toxcore/blob/master/other/bootstrap_daemon/tox-bootstrapd.conf"/>
-                and <link xlink:href="http://wiki.tox.im/Nodes"/>.
+                See <https://github.com/irungentoo/toxcore/blob/master/other/bootstrap_daemon/tox-bootstrapd.conf>
+                and <http://wiki.tox.im/Nodes>.
              '';
           };
       };
diff --git a/nixos/modules/services/networking/tox-node.nix b/nixos/modules/services/networking/tox-node.nix
index c6e5c2d6e819..884fd55dae51 100644
--- a/nixos/modules/services/networking/tox-node.nix
+++ b/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,47 +28,47 @@ 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" ];
       default = "Stderr";
-      description = "Logging implementation.";
+      description = lib.mdDoc "Logging implementation.";
     };
     keysFile = mkOption {
       type = types.str;
       default = "${homeDir}/keys";
-      description = "Path to the file where DHT keys are stored.";
+      description = lib.mdDoc "Path to the file where DHT keys are stored.";
     };
     udpAddress = mkOption {
       type = types.str;
       default = "0.0.0.0:33445";
-      description = "UDP address to run DHT node.";
+      description = lib.mdDoc "UDP address to run DHT node.";
     };
     tcpAddresses = mkOption {
       type = types.listOf types.str;
       default = [ "0.0.0.0:33445" ];
-      description = "TCP addresses to run TCP relay.";
+      description = lib.mdDoc "TCP addresses to run TCP relay.";
     };
     tcpConnectionLimit = mkOption {
       type = types.int;
       default = 8192;
-      description = "Maximum number of active TCP connections relay can hold";
+      description = lib.mdDoc "Maximum number of active TCP connections relay can hold";
     };
     lanDiscovery = mkOption {
       type = types.bool;
       default = true;
-      description = "Enable local network discovery.";
+      description = lib.mdDoc "Enable local network discovery.";
     };
     threads = mkOption {
       type = types.int;
       default = 1;
-      description = "Number of threads for execution";
+      description = lib.mdDoc "Number of threads for execution";
     };
     motd = mkOption {
       type = types.str;
       default = "Hi from tox-rs! I'm up {{uptime}}. TCP: incoming {{tcp_packets_in}}, outgoing {{tcp_packets_out}}, UDP: incoming {{udp_packets_in}}, outgoing {{udp_packets_out}}";
-      description = "Message of the day";
+      description = lib.mdDoc "Message of the day";
     };
   };
 
diff --git a/nixos/modules/services/networking/toxvpn.nix b/nixos/modules/services/networking/toxvpn.nix
index 18cf7672d5f4..3a14b5f73091 100644
--- a/nixos/modules/services/networking/toxvpn.nix
+++ b/nixos/modules/services/networking/toxvpn.nix
@@ -5,25 +5,25 @@ with lib;
 {
   options = {
     services.toxvpn = {
-      enable = mkEnableOption "toxvpn running on startup";
+      enable = mkEnableOption (lib.mdDoc "toxvpn running on startup");
 
       localip = mkOption {
         type        = types.str;
         default     = "10.123.123.1";
-        description = "your ip on the vpn";
+        description = lib.mdDoc "your ip on the vpn";
       };
 
       port = mkOption {
-        type        = types.int;
+        type        = types.port;
         default     = 33445;
-        description = "udp port for toxcore, port-forward to help with connectivity if you run many nodes behind one NAT";
+        description = lib.mdDoc "udp port for toxcore, port-forward to help with connectivity if you run many nodes behind one NAT";
       };
 
       auto_add_peers = mkOption {
         type        = types.listOf types.str;
         default     = [];
         example     = [ "toxid1" "toxid2" ];
-        description = "peers to automatically connect to on startup";
+        description = lib.mdDoc "peers to automatically connect to on startup";
       };
     };
   };
diff --git a/nixos/modules/services/networking/trickster.nix b/nixos/modules/services/networking/trickster.nix
index e48bba8fa587..0b696e412b4d 100644
--- a/nixos/modules/services/networking/trickster.nix
+++ b/nixos/modules/services/networking/trickster.nix
@@ -6,13 +6,16 @@ let
   cfg = config.services.trickster;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "trickster" "origin" ] [ "services" "trickster" "origin-url" ])
+  ];
 
   options = {
     services.trickster = {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Trickster.
         '';
       };
@@ -21,7 +24,7 @@ in
         type = types.package;
         default = pkgs.trickster;
         defaultText = literalExpression "pkgs.trickster";
-        description = ''
+        description = lib.mdDoc ''
           Package that should be used for trickster.
         '';
       };
@@ -29,7 +32,7 @@ in
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to configuration file.
         '';
       };
@@ -37,7 +40,7 @@ in
       instance-id = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Instance ID for when running multiple processes (default null).
         '';
       };
@@ -45,7 +48,7 @@ in
       log-level = mkOption {
         type = types.str;
         default = "info";
-        description = ''
+        description = lib.mdDoc ''
           Level of Logging to use (debug, info, warn, error) (default "info").
         '';
       };
@@ -53,23 +56,31 @@ in
       metrics-port = mkOption {
         type = types.port;
         default = 8082;
-        description = ''
+        description = lib.mdDoc ''
           Port that the /metrics endpoint will listen on.
         '';
       };
 
-      origin = mkOption {
+      origin-type = mkOption {
+        type = types.enum [ "prometheus" "influxdb" ];
+        default = "prometheus";
+        description = lib.mdDoc ''
+          Type of origin (prometheus, influxdb)
+        '';
+      };
+
+      origin-url = mkOption {
         type = types.str;
         default = "http://prometheus:9090";
-        description = ''
-          URL to the Prometheus Origin. Enter it like you would in grafana, e.g., http://prometheus:9090 (default http://prometheus:9090).
+        description = lib.mdDoc ''
+          URL to the Origin. Enter it like you would in grafana, e.g., http://prometheus:9090 (default http://prometheus:9090).
         '';
       };
 
       profiler-port = mkOption {
         type = types.nullOr types.port;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Port that the /debug/pprof endpoint will listen on.
         '';
       };
@@ -77,7 +88,7 @@ in
       proxy-port = mkOption {
         type = types.port;
         default = 9090;
-        description = ''
+        description = lib.mdDoc ''
           Port that the Proxy server will listen on.
         '';
       };
@@ -87,7 +98,7 @@ in
 
   config = mkIf cfg.enable {
     systemd.services.trickster = {
-      description = "Dashboard Accelerator for Prometheus";
+      description = "Reverse proxy cache and time series dashboard accelerator";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
@@ -96,7 +107,8 @@ in
           ${cfg.package}/bin/trickster \
           -log-level ${cfg.log-level} \
           -metrics-port ${toString cfg.metrics-port} \
-          -origin ${cfg.origin} \
+          -origin-type ${cfg.origin-type} \
+          -origin-url ${cfg.origin-url} \
           -proxy-port ${toString cfg.proxy-port} \
           ${optionalString (cfg.configFile != null) "-config ${cfg.configFile}"} \
           ${optionalString (cfg.profiler-port != null) "-profiler-port ${cfg.profiler-port}"} \
diff --git a/nixos/modules/services/networking/tvheadend.nix b/nixos/modules/services/networking/tvheadend.nix
index 19a10a03bd9b..466dbbccad53 100644
--- a/nixos/modules/services/networking/tvheadend.nix
+++ b/nixos/modules/services/networking/tvheadend.nix
@@ -9,17 +9,17 @@ in
 {
   options = {
     services.tvheadend = {
-      enable = mkEnableOption "Tvheadend";
+      enable = mkEnableOption (lib.mdDoc "Tvheadend");
       httpPort = mkOption {
         type        = types.int;
         default     = 9981;
-        description = "Port to bind HTTP to.";
+        description = lib.mdDoc "Port to bind HTTP to.";
       };
 
       htspPort = mkOption {
         type        = types.int;
         default     = 9982;
-        description = "Port to bind HTSP to.";
+        description = lib.mdDoc "Port to bind HTSP to.";
       };
     };
   };
diff --git a/nixos/modules/services/networking/twingate.nix b/nixos/modules/services/networking/twingate.nix
new file mode 100644
index 000000000000..17140bffd218
--- /dev/null
+++ b/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/nixos/modules/services/networking/ucarp.nix b/nixos/modules/services/networking/ucarp.nix
index 189e4f99cefe..1214cec63f54 100644
--- a/nixos/modules/services/networking/ucarp.nix
+++ b/nixos/modules/services/networking/ucarp.nix
@@ -28,34 +28,34 @@ 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;
-      description = "Network interface to bind to.";
+      description = lib.mdDoc "Network interface to bind to.";
       example = "eth0";
     };
 
     srcIp = mkOption {
       type = types.str;
-      description = "Source (real) IP address of this host.";
+      description = lib.mdDoc "Source (real) IP address of this host.";
     };
 
     vhId = mkOption {
       type = types.ints.between 1 255;
-      description = "Virtual IP identifier shared between CARP hosts.";
+      description = lib.mdDoc "Virtual IP identifier shared between CARP hosts.";
       example = 1;
     };
 
     passwordFile = mkOption {
       type = types.str;
-      description = "File containing shared password between CARP hosts.";
+      description = lib.mdDoc "File containing shared password between CARP hosts.";
       example = "/run/keys/ucarp-password";
     };
 
     preempt = mkOption {
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Enable preemptive failover.
         Thus, this host becomes the CARP master as soon as possible.
       '';
@@ -64,30 +64,30 @@ in {
 
     neutral = mkOption {
       type = types.bool;
-      description = "Do not run downscript at start if the host is the backup.";
+      description = lib.mdDoc "Do not run downscript at start if the host is the backup.";
       default = false;
     };
 
     addr = mkOption {
       type = types.str;
-      description = "Virtual shared IP address.";
+      description = lib.mdDoc "Virtual shared IP address.";
     };
 
     advBase = mkOption {
       type = types.ints.unsigned;
-      description = "Advertisement frequency in seconds.";
+      description = lib.mdDoc "Advertisement frequency in seconds.";
       default = 1;
     };
 
     advSkew = mkOption {
       type = types.ints.unsigned;
-      description = "Advertisement skew in seconds.";
+      description = lib.mdDoc "Advertisement skew in seconds.";
       default = 0;
     };
 
     upscript = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Command to run after become master, the interface name, virtual address
         and optional extra parameters are passed as arguments.
       '';
@@ -101,7 +101,7 @@ in {
 
     downscript = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Command to run after become backup, the interface name, virtual address
         and optional extra parameters are passed as arguments.
       '';
@@ -115,37 +115,37 @@ in {
 
     deadratio = mkOption {
       type = types.ints.unsigned;
-      description = "Ratio to consider a host as dead.";
+      description = lib.mdDoc "Ratio to consider a host as dead.";
       default = 3;
     };
 
     shutdown = mkOption {
       type = types.bool;
-      description = "Call downscript at exit.";
+      description = lib.mdDoc "Call downscript at exit.";
       default = false;
     };
 
     ignoreIfState = mkOption {
       type = types.bool;
-      description = "Ignore interface state, e.g., down or no carrier.";
+      description = lib.mdDoc "Ignore interface state, e.g., down or no carrier.";
       default = false;
     };
 
     noMcast = mkOption {
       type = types.bool;
-      description = "Use broadcast instead of multicast advertisements.";
+      description = lib.mdDoc "Use broadcast instead of multicast advertisements.";
       default = false;
     };
 
     extraParam = mkOption {
       type = types.nullOr types.str;
-      description = "Extra parameter to pass to the up/down scripts.";
+      description = lib.mdDoc "Extra parameter to pass to the up/down scripts.";
       default = null;
     };
 
     package = mkOption {
       type = types.package;
-      description = ''
+      description = lib.mdDoc ''
         Package that should be used for ucarp.
 
         Please note that the default package, pkgs.ucarp, has not received any
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 87873c8c1e83..c85dd03867f7 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -40,37 +40,37 @@ in {
   options = {
     services.unbound = {
 
-      enable = mkEnableOption "Unbound domain name server";
+      enable = mkEnableOption (lib.mdDoc "Unbound domain name server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.unbound-with-systemd;
         defaultText = literalExpression "pkgs.unbound-with-systemd";
-        description = "The unbound package to use";
+        description = lib.mdDoc "The unbound package to use";
       };
 
       user = mkOption {
         type = types.str;
         default = "unbound";
-        description = "User account under which unbound runs.";
+        description = lib.mdDoc "User account under which unbound runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "unbound";
-        description = "Group under which unbound runs.";
+        description = lib.mdDoc "Group under which unbound runs.";
       };
 
       stateDir = mkOption {
         type = types.path;
         default = "/var/lib/unbound";
-        description = "Directory holding all state for unbound to run.";
+        description = lib.mdDoc "Directory holding all state for unbound to run.";
       };
 
       resolveLocalQueries = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether unbound should resolve local queries (i.e. add 127.0.0.1 to
           /etc/resolv.conf).
         '';
@@ -79,7 +79,7 @@ in {
       enableRootTrustAnchor = mkOption {
         default = true;
         type = types.bool;
-        description = "Use and update root trust anchor for DNSSEC validation.";
+        description = lib.mdDoc "Use and update root trust anchor for DNSSEC validation.";
       };
 
       localControlSocketPath = mkOption {
@@ -90,16 +90,16 @@ in {
         # but I haven't verified yet.
         type = types.nullOr types.str;
         example = "/run/unbound/unbound.ctl";
-        description = ''
-          When not set to <literal>null</literal> this option defines the path
+        description = lib.mdDoc ''
+          When not set to `null` this option defines the path
           at which the unbound remote control socket should be created at. The
-          socket will be owned by the unbound user (<literal>unbound</literal>)
-          and group will be <literal>nogroup</literal>.
+          socket will be owned by the unbound user (`unbound`)
+          and group will be `nogroup`.
 
           Users that should be permitted to access the socket must be in the
-          <literal>config.services.unbound.group</literal> group.
+          `config.services.unbound.group` group.
 
-          If this option is <literal>null</literal> remote control will not be
+          If this option is `null` remote control will not be
           enabled. Unbounds default values apply.
         '';
       };
@@ -150,10 +150,9 @@ in {
             remote-control.control-enable = true;
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Declarative Unbound configuration
-          See the <citerefentry><refentrytitle>unbound.conf</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> manpage for a list of
+          See the {manpage}`unbound.conf(5)` manpage for a list of
           available options.
         '';
       };
@@ -246,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"
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index a683c537f05b..d220aa9fbbe4 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -17,16 +17,16 @@ in
     services.unifi.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether or not to enable the unifi controller service.
       '';
     };
 
     services.unifi.jrePackage = mkOption {
       type = types.package;
-      default = pkgs.jre8;
-      defaultText = literalExpression "pkgs.jre8";
-      description = ''
+      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.
       '';
     };
@@ -35,7 +35,7 @@ in
       type = types.package;
       default = pkgs.unifiLTS;
       defaultText = literalExpression "pkgs.unifiLTS";
-      description = ''
+      description = lib.mdDoc ''
         The unifi package to use.
       '';
     };
@@ -44,15 +44,15 @@ in
       type = types.package;
       default = pkgs.mongodb;
       defaultText = literalExpression "pkgs.mongodb";
-      description = ''
+      description = lib.mdDoc ''
         The mongodb package to use.
       '';
     };
 
     services.unifi.openFirewall = mkOption {
       type = types.bool;
-      default = true;
-      description = ''
+      default = false;
+      description = lib.mdDoc ''
         Whether or not to open the minimum required ports on the firewall.
 
         This is necessary to allow firmware upgrades and device discovery to
@@ -65,7 +65,7 @@ in
       type = types.nullOr types.int;
       default = null;
       example = 1024;
-      description = ''
+      description = lib.mdDoc ''
         Set the initial heap size for the JVM in MB. If this option isn't set, the
         JVM will decide this value at runtime.
       '';
@@ -75,8 +75,8 @@ in
       type = types.nullOr types.int;
       default = null;
       example = 4096;
-      description = ''
-        Set the maximimum heap size for the JVM in MB. If this option isn't set, the
+      description = lib.mdDoc ''
+        Set the maximum heap size for the JVM in MB. If this option isn't set, the
         JVM will decide this value at runtime.
       '';
     };
@@ -85,10 +85,6 @@ in
 
   config = mkIf cfg.enable {
 
-    warnings = optional
-      (options.services.unifi.openFirewall.highestPrio >= (mkOptionDefault null).priority)
-      "The current services.unifi.openFirewall = true default is deprecated and will change to false in 22.11. Set it explicitly to silence this warning.";
-
     users.users.unifi = {
       isSystemUser = true;
       group = "unifi";
diff --git a/nixos/modules/services/networking/uptermd.nix b/nixos/modules/services/networking/uptermd.nix
new file mode 100644
index 000000000000..f824d617f59e
--- /dev/null
+++ b/nixos/modules/services/networking/uptermd.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.uptermd;
+in
+{
+  options = {
+    services.uptermd = {
+      enable = mkEnableOption (lib.mdDoc "uptermd");
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to open the firewall for the port in {option}`services.uptermd.port`.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 2222;
+        description = lib.mdDoc ''
+          Port the server will listen on.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "[::]";
+        example = "127.0.0.1";
+        description = lib.mdDoc ''
+          Address the server will listen on.
+        '';
+      };
+
+      hostKey = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/upterm_host_ed25519_key";
+        description = lib.mdDoc ''
+          Path to SSH host key. If not defined, an ed25519 keypair is generated automatically.
+        '';
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--debug" ];
+        description = lib.mdDoc ''
+          Extra flags passed to the uptermd command.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+
+    systemd.services.uptermd = {
+      description = "Upterm Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [ pkgs.openssh ];
+
+      preStart = mkIf (cfg.hostKey == null) ''
+        if ! [ -f ssh_host_ed25519_key ]; then
+          ssh-keygen \
+            -t ed25519 \
+            -f ssh_host_ed25519_key \
+            -N ""
+        fi
+      '';
+
+      serviceConfig = {
+        StateDirectory = "uptermd";
+        WorkingDirectory = "/var/lib/uptermd";
+        ExecStart = "${pkgs.upterm}/bin/uptermd --ssh-addr ${cfg.listenAddress}:${toString cfg.port} --private-key ${if cfg.hostKey == null then "ssh_host_ed25519_key" else cfg.hostKey} ${concatStringsSep " " cfg.extraFlags}";
+
+        # Hardening
+        AmbientCapabilities = mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        PrivateUsers = cfg.port >= 1024;
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        # AF_UNIX is for ssh-keygen, which relies on nscd to resolve the uid to a user
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "@system-service";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/v2ray.nix b/nixos/modules/services/networking/v2ray.nix
index 95e8761ba5cc..ba2aa5bc1de7 100644
--- a/nixos/modules/services/networking/v2ray.nix
+++ b/nixos/modules/services/networking/v2ray.nix
@@ -9,10 +9,10 @@ with lib;
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to run v2ray server.
 
-          Either <literal>configFile</literal> or <literal>config</literal> must be specified.
+          Either `configFile` or `config` must be specified.
         '';
       };
 
@@ -20,7 +20,7 @@ with lib;
         type = types.package;
         default = pkgs.v2ray;
         defaultText = literalExpression "pkgs.v2ray";
-        description = ''
+        description = lib.mdDoc ''
           Which v2ray package to use.
         '';
       };
@@ -29,12 +29,12 @@ with lib;
         type = types.nullOr types.str;
         default = null;
         example = "/etc/v2ray/config.json";
-        description = ''
+        description = lib.mdDoc ''
           The absolute path to the configuration file.
 
-          Either <literal>configFile</literal> or <literal>config</literal> must be specified.
+          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>.
         '';
       };
 
@@ -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/nixos/modules/services/networking/v2raya.nix b/nixos/modules/services/networking/v2raya.nix
new file mode 100644
index 000000000000..2d697b4fb56f
--- /dev/null
+++ b/nixos/modules/services/networking/v2raya.nix
@@ -0,0 +1,39 @@
+{ 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 = {
+      unitConfig = {
+        Description = "v2rayA service";
+        Documentation = "https://github.com/v2rayA/v2rayA/wiki";
+        After = [ "network.target" "nss-lookup.target" "iptables.service" "ip6tables.service" ];
+        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/nixos/modules/services/networking/vdirsyncer.nix b/nixos/modules/services/networking/vdirsyncer.nix
new file mode 100644
index 000000000000..6a069943434d
--- /dev/null
+++ b/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 = mkPackageOption 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/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix
index d205302051e1..b1f0f7403243 100644
--- a/nixos/modules/services/networking/vsftpd.nix
+++ b/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,33 +150,33 @@ in
 
     services.vsftpd = {
 
-      enable = mkEnableOption "vsftpd";
+      enable = mkEnableOption (lib.mdDoc "vsftpd");
 
       userlist = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = "See <option>userlistFile</option>.";
+        description = lib.mdDoc "See {option}`userlistFile`.";
       };
 
       userlistFile = mkOption {
         type = types.path;
         default = pkgs.writeText "userlist" (concatMapStrings (x: "${x}\n") cfg.userlist);
         defaultText = literalExpression ''pkgs.writeText "userlist" (concatMapStrings (x: "''${x}\n") cfg.userlist)'';
-        description = ''
-          Newline separated list of names to be allowed/denied if <option>userlistEnable</option>
-          is <literal>true</literal>. Meaning see <option>userlistDeny</option>.
+        description = lib.mdDoc ''
+          Newline separated list of names to be allowed/denied if {option}`userlistEnable`
+          is `true`. Meaning see {option}`userlistDeny`.
 
-          The default is a file containing the users from <option>userlist</option>.
+          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.
         '';
       };
 
       enableVirtualUsers = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to enable the <literal>pam_userdb</literal>-based
+        description = lib.mdDoc ''
+          Whether to enable the `pam_userdb`-based
           virtual user system
         '';
       };
@@ -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.
         '';
@@ -218,7 +219,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "/var/www/$USER";
-        description = ''
+        description = lib.mdDoc ''
           This option represents a directory which vsftpd will try to
           change into after a local (i.e. non- anonymous) login.
 
@@ -229,7 +230,7 @@ in
       anonymousUserHome = mkOption {
         type = types.path;
         default = "/home/ftp/";
-        description = ''
+        description = lib.mdDoc ''
           Directory to consider the HOME of the anonymous user.
         '';
       };
@@ -237,27 +238,27 @@ in
       rsaCertFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "RSA certificate file.";
+        description = lib.mdDoc "RSA certificate file.";
       };
 
       rsaKeyFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "RSA private key file.";
+        description = lib.mdDoc "RSA private key file.";
       };
 
       anonymousUmask = mkOption {
         type = types.str;
         default = "077";
         example = "002";
-        description = "Anonymous write umask.";
+        description = lib.mdDoc "Anonymous write umask.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
         example = "ftpd_banner=Hello";
-        description = "Extra configuration to add at the bottom of the generated configuration file.";
+        description = lib.mdDoc "Extra configuration to add at the bottom of the generated configuration file.";
       };
 
     } // (listToAttrs (catAttrs "nixosOption" optionDescription));
diff --git a/nixos/modules/services/networking/wasabibackend.nix b/nixos/modules/services/networking/wasabibackend.nix
index b6dcd940915a..938145b35ee8 100644
--- a/nixos/modules/services/networking/wasabibackend.nix
+++ b/nixos/modules/services/networking/wasabibackend.nix
@@ -29,37 +29,37 @@ in {
   options = {
 
     services.wasabibackend = {
-      enable = mkEnableOption "Wasabi backend service";
+      enable = mkEnableOption (lib.mdDoc "Wasabi backend service");
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/wasabibackend";
-        description = "The data directory for the Wasabi backend node.";
+        description = lib.mdDoc "The data directory for the Wasabi backend node.";
       };
 
       customConfigFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "Defines the path to a custom configuration file that is copied to the user's directory. Overrides any config options.";
+        description = lib.mdDoc "Defines the path to a custom configuration file that is copied to the user's directory. Overrides any config options.";
       };
 
       network = mkOption {
         type = types.enum [ "mainnet" "testnet" "regtest" ];
         default = "mainnet";
-        description = "The network to use for the Wasabi backend service.";
+        description = lib.mdDoc "The network to use for the Wasabi backend service.";
       };
 
       endpoint = {
         ip = mkOption {
           type = types.str;
           default = "127.0.0.1";
-          description = "IP address for P2P connection to bitcoind.";
+          description = lib.mdDoc "IP address for P2P connection to bitcoind.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8333;
-          description = "Port for P2P connection to bitcoind.";
+          description = lib.mdDoc "Port for P2P connection to bitcoind.";
         };
       };
 
@@ -67,45 +67,45 @@ in {
         ip = mkOption {
           type = types.str;
           default = "127.0.0.1";
-          description = "IP address for RPC connection to bitcoind.";
+          description = lib.mdDoc "IP address for RPC connection to bitcoind.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8332;
-          description = "Port for RPC connection to bitcoind.";
+          description = lib.mdDoc "Port for RPC connection to bitcoind.";
         };
 
         user = mkOption {
           type = types.str;
           default = "bitcoin";
-          description = "RPC user for the bitcoin endpoint.";
+          description = lib.mdDoc "RPC user for the bitcoin endpoint.";
         };
 
         password = mkOption {
           type = types.str;
           default = "password";
-          description = "RPC password for the bitcoin endpoint. Warning: this is stored in cleartext in the Nix store! Use <literal>configFile</literal> or <literal>passwordFile</literal> if needed.";
+          description = lib.mdDoc "RPC password for the bitcoin endpoint. Warning: this is stored in cleartext in the Nix store! Use `configFile` or `passwordFile` if needed.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
-          description = "File that contains the password of the RPC user.";
+          description = lib.mdDoc "File that contains the password of the RPC user.";
         };
       };
 
       user = mkOption {
         type = types.str;
         default = "wasabibackend";
-        description = "The user as which to run the wasabibackend node.";
+        description = lib.mdDoc "The user as which to run the wasabibackend node.";
       };
 
       group = mkOption {
         type = types.str;
         default = cfg.user;
         defaultText = literalExpression "config.${opt.user}";
-        description = "The group as which to run the wasabibackend node.";
+        description = lib.mdDoc "The group as which to run the wasabibackend node.";
       };
     };
   };
diff --git a/nixos/modules/services/networking/websockify.nix b/nixos/modules/services/networking/websockify.nix
index f7e014e03efb..45a3487bd337 100644
--- a/nixos/modules/services/networking/websockify.nix
+++ b/nixos/modules/services/networking/websockify.nix
@@ -6,7 +6,7 @@ let cfg = config.services.networking.websockify; in {
   options = {
     services.networking.websockify = {
       enable = mkOption {
-        description = "Whether to enable websockify to forward websocket connections to TCP connections.";
+        description = lib.mdDoc "Whether to enable websockify to forward websocket connections to TCP connections.";
 
         default = false;
 
@@ -14,19 +14,19 @@ let cfg = config.services.networking.websockify; in {
       };
 
       sslCert = mkOption {
-        description = "Path to the SSL certificate.";
+        description = lib.mdDoc "Path to the SSL certificate.";
         type = types.path;
       };
 
       sslKey = mkOption {
-        description = "Path to the SSL key.";
+        description = lib.mdDoc "Path to the SSL key.";
         default = cfg.sslCert;
         defaultText = literalExpression "config.services.networking.websockify.sslCert";
         type = types.path;
       };
 
       portMap = mkOption {
-        description = "Ports to map by default.";
+        description = lib.mdDoc "Ports to map by default.";
         default = {};
         type = types.attrsOf types.int;
       };
diff --git a/nixos/modules/services/networking/wg-netmanager.nix b/nixos/modules/services/networking/wg-netmanager.nix
index 493ff7ceba9f..b260c573726b 100644
--- a/nixos/modules/services/networking/wg-netmanager.nix
+++ b/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/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index 61e9fe5096b1..a678d743bb77 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -10,15 +10,27 @@ let
 
   interfaceOpts = { ... }: {
     options = {
+
+      configFile = mkOption {
+        example = "/secret/wg0.conf";
+        default = null;
+        type = with types; nullOr str;
+        description = lib.mdDoc ''
+          wg-quick .conf file, describing the interface.
+          This overrides any other configuration interface configuration options.
+          See wg-quick manpage for more details.
+        '';
+      };
+
       address = mkOption {
         example = [ "192.168.2.1/24" ];
         default = [];
         type = with types; listOf str;
-        description = "The IP addresses of the interface.";
+        description = lib.mdDoc "The IP addresses of the interface.";
       };
 
       autostart = mkOption {
-        description = "Whether to bring up this interface automatically during boot.";
+        description = lib.mdDoc "Whether to bring up this interface automatically during boot.";
         default = true;
         example = false;
         type = types.bool;
@@ -28,15 +40,15 @@ let
         example = [ "192.168.2.2" ];
         default = [];
         type = with types; listOf str;
-        description = "The IP addresses of DNS servers to configure.";
+        description = lib.mdDoc "The IP addresses of DNS servers to configure.";
       };
 
       privateKey = mkOption {
         example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
         type = with types; nullOr str;
         default = null;
-        description = ''
-          Base64 private key generated by <command>wg genkey</command>.
+        description = lib.mdDoc ''
+          Base64 private key generated by {command}`wg genkey`.
 
           Warning: Consider using privateKeyFile instead if you do not
           want to store the key in the world-readable Nix store.
@@ -47,8 +59,8 @@ let
         example = "/private/wireguard_key";
         type = with types; nullOr str;
         default = null;
-        description = ''
-          Private key file as generated by <command>wg genkey</command>.
+        description = lib.mdDoc ''
+          Private key file as generated by {command}`wg genkey`.
         '';
       };
 
@@ -56,7 +68,7 @@ let
         default = null;
         type = with types; nullOr int;
         example = 51820;
-        description = ''
+        description = lib.mdDoc ''
           16-bit port for listening. Optional; if not specified,
           automatically generated based on interface name.
         '';
@@ -66,7 +78,7 @@ let
         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
-        description = ''
+        description = lib.mdDoc ''
           Commands called at the start of the interface setup.
         '';
       };
@@ -75,7 +87,7 @@ let
         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
-        description = ''
+        description = lib.mdDoc ''
           Command called before the interface is taken down.
         '';
       };
@@ -84,7 +96,7 @@ let
         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
-        description = ''
+        description = lib.mdDoc ''
           Commands called after the interface setup.
         '';
       };
@@ -93,7 +105,7 @@ let
         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
-        description = ''
+        description = lib.mdDoc ''
           Command called after the interface is taken down.
         '';
       };
@@ -102,7 +114,7 @@ let
         example = "main";
         default = null;
         type = with types; nullOr str;
-        description = ''
+        description = lib.mdDoc ''
           The kernel routing table to add this interface's
           associated routes to. Setting this is useful for e.g. policy routing
           ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
@@ -115,7 +127,7 @@ let
         example = 1248;
         default = null;
         type = with types; nullOr int;
-        description = ''
+        description = lib.mdDoc ''
           If not specified, the MTU is automatically determined
           from the endpoint addresses or the system default route, which is usually
           a sane choice. However, to manually specify an MTU to override this
@@ -125,7 +137,7 @@ let
 
       peers = mkOption {
         default = [];
-        description = "Peers linked to the interface.";
+        description = lib.mdDoc "Peers linked to the interface.";
         type = with types; listOf (submodule peerOpts);
       };
     };
@@ -138,15 +150,15 @@ let
       publicKey = mkOption {
         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
         type = types.str;
-        description = "The base64 public key to the peer.";
+        description = lib.mdDoc "The base64 public key to the peer.";
       };
 
       presharedKey = mkOption {
         default = null;
         example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
         type = with types; nullOr str;
-        description = ''
-          Base64 preshared key generated by <command>wg genpsk</command>.
+        description = lib.mdDoc ''
+          Base64 preshared key generated by {command}`wg genpsk`.
           Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
@@ -160,8 +172,8 @@ let
         default = null;
         example = "/private/wireguard_psk";
         type = with types; nullOr str;
-        description = ''
-          File pointing to preshared key as generated by <command>wg genpsk</command>.
+        description = lib.mdDoc ''
+          File pointing to preshared key as generated by {command}`wg genpsk`.
           Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
@@ -171,7 +183,7 @@ let
       allowedIPs = mkOption {
         example = [ "10.192.122.3/32" "10.192.124.1/24" ];
         type = with types; listOf str;
-        description = ''List of IP (v4 or v6) addresses with CIDR masks from
+        description = lib.mdDoc ''List of IP (v4 or v6) addresses with CIDR masks from
         which this peer is allowed to send incoming traffic and to which
         outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
         be specified for matching all IPv4 addresses, and ::/0 may be specified
@@ -182,7 +194,7 @@ 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,
+        description = lib.mdDoc ''Endpoint IP or hostname of the peer, followed by a colon,
         and then a port number of the peer.'';
       };
 
@@ -190,7 +202,7 @@ let
         default = null;
         type = with types; nullOr int;
         example = 25;
-        description = ''This is optional and is by default off, because most
+        description = lib.mdDoc ''This is optional and is by default off, because most
         users will not need it. It represents, in seconds, between 1 and 65535
         inclusive, how often to send an authenticated empty packet to the peer,
         for the purpose of keeping a stateful firewall or NAT mapping valid
@@ -205,13 +217,13 @@ let
   writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}");
 
   generateUnit = name: values:
-    assert assertMsg ((values.privateKey != null) != (values.privateKeyFile != null)) "Only one of privateKey or privateKeyFile may be set";
+    assert assertMsg (values.configFile != null || ((values.privateKey != null) != (values.privateKeyFile != null))) "Only one of privateKey, configFile or privateKeyFile may be set";
     let
       preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null;
       postUp =
             optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++
             (concatMap (peer: optional (peer.presharedKeyFile != null) "wg set ${name} peer ${peer.publicKey} preshared-key <(cat ${peer.presharedKeyFile})") values.peers) ++
-            optional (values.postUp != null) values.postUp;
+            optional (values.postUp != "") values.postUp;
       postUpFile = if postUp != [] then writeScriptFile "postUp.sh" (concatMapStringsSep "\n" (line: line) postUp) else null;
       preDownFile = if values.preDown != "" then writeScriptFile "preDown.sh" values.preDown else null;
       postDownFile = if values.postDown != "" then writeScriptFile "postDown.sh" values.postDown else null;
@@ -247,7 +259,12 @@ let
           optionalString (peer.allowedIPs != []) "AllowedIPs = ${concatStringsSep "," peer.allowedIPs}\n"
         ) values.peers;
       };
-      configPath = "${configDir}/${name}.conf";
+      configPath =
+        if values.configFile != null then
+          # This uses bind-mounted private tmp folder (/tmp/systemd-private-***)
+          "/tmp/${name}.conf"
+        else
+          "${configDir}/${name}.conf";
     in
     nameValuePair "wg-quick-${name}"
       {
@@ -256,7 +273,7 @@ let
         after = [ "network.target" "network-online.target" ];
         wantedBy = optional values.autostart "multi-user.target";
         environment.DEVICE = name;
-        path = [ pkgs.kmod pkgs.wireguard-tools ];
+        path = [ pkgs.kmod pkgs.wireguard-tools config.networking.resolvconf.package ];
 
         serviceConfig = {
           Type = "oneshot";
@@ -265,9 +282,17 @@ let
 
         script = ''
           ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
+          ${optionalString (values.configFile != null) ''
+            cp ${values.configFile} ${configPath}
+          ''}
           wg-quick up ${configPath}
         '';
 
+        serviceConfig = {
+          # Used to privately store renamed copies of external config files during activation
+          PrivateTmp = true;
+        };
+
         preStop = ''
           wg-quick down ${configPath}
         '';
@@ -279,7 +304,7 @@ in {
   options = {
     networking.wg-quick = {
       interfaces = mkOption {
-        description = "Wireguard interfaces.";
+        description = lib.mdDoc "Wireguard interfaces.";
         default = {};
         example = {
           wg0 = {
@@ -303,9 +328,12 @@ 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).
+    systemd.network.config.networkConfig.ManageForeignRoutingPolicyRules = mkDefault false;
+
+    # WireGuard interfaces should be ignored in determining whether the network is online.
+    systemd.network.wait-online.ignoredInterfaces = builtins.attrNames cfg.interfaces;
   };
 }
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 7cd44b2f8a0a..1d6556f626be 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -19,15 +19,15 @@ let
         example = [ "192.168.2.1/24" ];
         default = [];
         type = with types; listOf str;
-        description = "The IP addresses of the interface.";
+        description = lib.mdDoc "The IP addresses of the interface.";
       };
 
       privateKey = mkOption {
         example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
         type = with types; nullOr str;
         default = null;
-        description = ''
-          Base64 private key generated by <command>wg genkey</command>.
+        description = lib.mdDoc ''
+          Base64 private key generated by {command}`wg genkey`.
 
           Warning: Consider using privateKeyFile instead if you do not
           want to store the key in the world-readable Nix store.
@@ -37,9 +37,9 @@ let
       generatePrivateKeyFile = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Automatically generate a private key with
-          <command>wg genkey</command>, at the privateKeyFile location.
+          {command}`wg genkey`, at the privateKeyFile location.
         '';
       };
 
@@ -47,8 +47,8 @@ let
         example = "/private/wireguard_key";
         type = with types; nullOr str;
         default = null;
-        description = ''
-          Private key file as generated by <command>wg genkey</command>.
+        description = lib.mdDoc ''
+          Private key file as generated by {command}`wg genkey`.
         '';
       };
 
@@ -56,7 +56,7 @@ let
         default = null;
         type = with types; nullOr int;
         example = 51820;
-        description = ''
+        description = lib.mdDoc ''
           16-bit port for listening. Optional; if not specified,
           automatically generated based on interface name.
         '';
@@ -66,7 +66,7 @@ let
         example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns add foo"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
-        description = ''
+        description = lib.mdDoc ''
           Commands called at the start of the interface setup.
         '';
       };
@@ -77,20 +77,20 @@ let
         '';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
-        description = "Commands called at the end of the interface setup.";
+        description = lib.mdDoc "Commands called at the end of the interface setup.";
       };
 
       postShutdown = mkOption {
         example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"'';
         default = "";
         type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
-        description = "Commands called after shutting down the interface.";
+        description = lib.mdDoc "Commands called after shutting down the interface.";
       };
 
       table = mkOption {
         default = "main";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The kernel routing table to add this interface's
           associated routes to. Setting this is useful for e.g. policy routing
           ("ip rule") or virtual routing and forwarding ("ip vrf"). Both
@@ -101,7 +101,7 @@ let
 
       peers = mkOption {
         default = [];
-        description = "Peers linked to the interface.";
+        description = lib.mdDoc "Peers linked to the interface.";
         type = with types; listOf (submodule peerOpts);
       };
 
@@ -109,7 +109,7 @@ let
         example = false;
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Determines whether to add allowed IPs as routes or not.
         '';
       };
@@ -118,12 +118,11 @@ let
         default = null;
         type = with types; nullOr str;
         example = "container";
-        description = ''The pre-existing network namespace in which the
+        description = lib.mdDoc ''The pre-existing network namespace in which the
         WireGuard interface is created, and which retains the socket even if the
-        interface is moved via <option>interfaceNamespace</option>. When
-        <literal>null</literal>, the interface is created in the init namespace.
-        See <link
-        xlink:href="https://www.wireguard.com/netns/">documentation</link>.
+        interface is moved via {option}`interfaceNamespace`. When
+        `null`, the interface is created in the init namespace.
+        See [documentation](https://www.wireguard.com/netns/).
         '';
       };
 
@@ -131,12 +130,38 @@ let
         default = null;
         type = with types; nullOr str;
         example = "init";
-        description = ''The pre-existing network namespace the WireGuard
-        interface is moved to. The special value <literal>init</literal> means
-        the init namespace. When <literal>null</literal>, the interface is not
+        description = lib.mdDoc ''The pre-existing network namespace the WireGuard
+        interface is moved to. The special value `init` means
+        the init namespace. When `null`, the interface is not
         moved.
-        See <link
-        xlink:href="https://www.wireguard.com/netns/">documentation</link>.
+        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.
         '';
       };
     };
@@ -152,15 +177,15 @@ let
       publicKey = mkOption {
         example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
         type = types.str;
-        description = "The base64 public key of the peer.";
+        description = lib.mdDoc "The base64 public key of the peer.";
       };
 
       presharedKey = mkOption {
         default = null;
         example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
         type = with types; nullOr str;
-        description = ''
-          Base64 preshared key generated by <command>wg genpsk</command>.
+        description = lib.mdDoc ''
+          Base64 preshared key generated by {command}`wg genpsk`.
           Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
@@ -174,8 +199,8 @@ let
         default = null;
         example = "/private/wireguard_psk";
         type = with types; nullOr str;
-        description = ''
-          File pointing to preshared key as generated by <command>wg genpsk</command>.
+        description = lib.mdDoc ''
+          File pointing to preshared key as generated by {command}`wg genpsk`.
           Optional, and may be omitted. This option adds an additional layer of
           symmetric-key cryptography to be mixed into the already existing
           public-key cryptography, for post-quantum resistance.
@@ -185,7 +210,7 @@ let
       allowedIPs = mkOption {
         example = [ "10.192.122.3/32" "10.192.124.1/24" ];
         type = with types; listOf str;
-        description = ''List of IP (v4 or v6) addresses with CIDR masks from
+        description = lib.mdDoc ''List of IP (v4 or v6) addresses with CIDR masks from
         which this peer is allowed to send incoming traffic and to which
         outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
         be specified for matching all IPv4 addresses, and ::/0 may be specified
@@ -196,19 +221,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.
         '';
       };
 
@@ -216,12 +242,27 @@ let
         default = 0;
         example = 5;
         type = with types; int;
-        description = ''
-          Periodically re-execute the <literal>wg</literal> utility every
+        description = lib.mdDoc ''
+          Periodically re-execute the `wg` utility every
           this many seconds in order to let WireGuard notice DNS / hostname
           changes.
 
-          Setting this to <literal>0</literal> disables periodic reexecution.
+          Setting this to `0` disables periodic reexecution.
+        '';
+      };
+
+      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.
         '';
       };
 
@@ -229,7 +270,7 @@ let
         default = null;
         type = with types; nullOr int;
         example = 25;
-        description = ''This is optional and is by default off, because most
+        description = lib.mdDoc ''This is optional and is by default off, because most
         users will not need it. It represents, in seconds, between 1 and 65535
         inclusive, how often to send an authenticated empty packet to the peer,
         for the purpose of keeping a stateful firewall or NAT mapping valid
@@ -262,7 +303,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
@@ -274,7 +315,7 @@ let
 
   peerUnitServiceName = interfaceName: publicKey: dynamicRefreshEnabled:
     let
-      keyToUnitName = replaceChars
+      keyToUnitName = replaceStrings
         [ "/" "-"    " "     "+"     "="      ]
         [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
       unitName = keyToUnitName publicKey;
@@ -301,8 +342,9 @@ let
       {
         description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
         requires = [ "wireguard-${interfaceName}.service" ];
-        after = [ "wireguard-${interfaceName}.service" ];
-        wantedBy = [ "multi-user.target" "wireguard-${interfaceName}.service" ];
+        wants = [ "network-online.target" ];
+        after = [ "wireguard-${interfaceName}.service" "network-online.target" ];
+        wantedBy = [ "wireguard-${interfaceName}.service" ];
         environment.DEVICE = interfaceName;
         environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
         path = with pkgs; [ iproute2 wireguard-tools ];
@@ -321,7 +363,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 " " (
@@ -364,6 +415,19 @@ let
         '';
       };
 
+  # the target is required to start new peer units when they are added
+  generateInterfaceTarget = name: values:
+    let
+      mkPeerUnit = peer: (peerUnitServiceName name peer.publicKey (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);
@@ -379,9 +443,9 @@ let
     nameValuePair "wireguard-${name}"
       {
         description = "WireGuard Tunnel - ${name}";
-        requires = [ "network-online.target" ];
-        after = [ "network.target" "network-online.target" ];
-        wantedBy = [ "multi-user.target" ];
+        after = [ "network-pre.target" ];
+        wants = [ "network.target" ];
+        before = [ "network.target" ];
         environment.DEVICE = name;
         path = with pkgs; [ kmod iproute2 wireguard-tools ];
 
@@ -397,6 +461,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) ''${ipPreMove} link set "${name}" mtu ${toString values.mtu}''}
 
           ${concatMapStringsSep "\n" (ip:
             ''${ipPostMove} address add "${ip}" dev "${name}"''
@@ -405,6 +470,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 +501,13 @@ in
     networking.wireguard = {
 
       enable = mkOption {
-        description = "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 +516,13 @@ in
       };
 
       interfaces = mkOption {
-        description = "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 +576,8 @@ in
       // (mapAttrs' generateKeyServiceUnit
       (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces));
 
-  });
+      systemd.targets = mapAttrs' generateInterfaceTarget cfg.interfaces;
+    }
+  );
 
 }
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index c2e1d37e28bf..119575bdddb4 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -114,7 +114,7 @@ let
 
       script =
       ''
-        ${optionalString configIsGenerated ''
+        ${optionalString (configIsGenerated && !cfg.allowAuxiliaryImperativeNetworks) ''
           if [ -f /etc/wpa_supplicant.conf ]; then
             echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
           fi
@@ -164,42 +164,42 @@ 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.
+          :::
         '';
       };
 
       driver = mkOption {
         type = types.str;
         default = "nl80211,wext";
-        description = "Force a specific wpa_supplicant driver.";
+        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`.
         '';
       };
 
       scanOnLowSignal = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to periodically scan for (better) networks when the signal of
           the current one is low. This will make roaming between access points
           faster, but will consume more power.
@@ -209,7 +209,7 @@ in {
       fallbackToWPA2 = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to fall back to WPA2 authentication protocols if WPA3 failed.
           This allows old wireless cards (that lack recent features required by
           WPA3) to connect to mixed WPA2/WPA3 access points.
@@ -222,26 +222,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 = {
@@ -252,7 +250,7 @@ in {
               password="@PASS_WORK@"
             ''';
           };
-          </programlisting>
+          ```
         '';
       };
 
@@ -262,36 +260,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`.
+                :::
               '';
             };
 
@@ -331,9 +329,9 @@ in {
                 "OWE"
                 "DPP"
               ]);
-              description = ''
+              description = lib.mdDoc ''
                 The list of authentication protocols accepted by this network.
-                This corresponds to the <literal>key_mgmt</literal> option in wpa_supplicant.
+                This corresponds to the `key_mgmt` option in wpa_supplicant.
               '';
             };
 
@@ -345,32 +343,29 @@ 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`.
+                :::
               '';
             };
 
             hidden = mkOption {
               type = types.bool;
               default = false;
-              description = ''
-                Set this to <literal>true</literal> if the SSID of the network is hidden.
+              description = lib.mdDoc ''
+                Set this to `true` if the SSID of the network is hidden.
               '';
               example = literalExpression ''
                 { echelon = {
@@ -384,7 +379,7 @@ in {
             priority = mkOption {
               type = types.nullOr types.int;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 By default, all networks will get same priority group (0). If some of the
                 networks are more desirable, this field can be used to change the order in
                 which wpa_supplicant goes through the networks when selecting a BSS. The
@@ -401,22 +396,19 @@ in {
               example = ''
                 bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66
               '';
-              description = ''
+              description = lib.mdDoc ''
                 Extra configuration lines appended to the network block.
                 See
-                <citerefentry>
-                  <refentrytitle>wpa_supplicant.conf</refentrytitle>
-                  <manvolnum>5</manvolnum>
-                </citerefentry>
+                {manpage}`wpa_supplicant.conf(5)`
                 for available options.
               '';
             };
 
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           The network definitions to automatically connect to when
-           <command>wpa_supplicant</command> is running. If this
+           {command}`wpa_supplicant` is running. If this
            parameter is left empty wpa_supplicant will use
           /etc/wpa_supplicant.conf as the configuration file.
         '';
@@ -443,7 +435,7 @@ in {
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
             This is useful for laptop users that switch networks a lot and don't want
             to depend on a large package such as NetworkManager just to pick nearby
@@ -458,7 +450,7 @@ in {
           type = types.str;
           default = "wheel";
           example = "network";
-          description = "Members of this group can control wpa_supplicant.";
+          description = lib.mdDoc "Members of this group can control wpa_supplicant.";
         };
       };
 
@@ -466,7 +458,7 @@ in {
         type = types.bool;
         default = lib.length cfg.interfaces < 2;
         defaultText = literalExpression "length config.${opt.interfaces} < 2";
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the DBus control interface.
           This is only needed when using NetworkManager or connman.
         '';
@@ -478,13 +470,10 @@ in {
         example = ''
           p2p_disabled=1
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra lines appended to the configuration file.
           See
-          <citerefentry>
-            <refentrytitle>wpa_supplicant.conf</refentrytitle>
-            <manvolnum>5</manvolnum>
-          </citerefentry>
+          {manpage}`wpa_supplicant.conf(5)`
           for available options.
         '';
       };
diff --git a/nixos/modules/services/networking/x2goserver.nix b/nixos/modules/services/networking/x2goserver.nix
index d4adf6c5650e..1242229a0b60 100644
--- a/nixos/modules/services/networking/x2goserver.nix
+++ b/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
@@ -42,7 +42,7 @@ in {
     nxagentDefaultOptions = mkOption {
       type = types.listOf types.str;
       default = [ "-extension GLX" "-nolisten tcp" ];
-      description = ''
+      description = lib.mdDoc ''
         List of default nx agent options.
       '';
     };
@@ -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/nixos/modules/services/networking/xandikos.nix b/nixos/modules/services/networking/xandikos.nix
index 4bd45a76e673..6d1ddc74c719 100644
--- a/nixos/modules/services/networking/xandikos.nix
+++ b/nixos/modules/services/networking/xandikos.nix
@@ -9,19 +9,19 @@ 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;
         default = pkgs.xandikos;
         defaultText = literalExpression "pkgs.xandikos";
-        description = "The Xandikos package to use.";
+        description = lib.mdDoc "The Xandikos package to use.";
       };
 
       address = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           The IP address on which Xandikos will listen.
           By default listens on localhost.
         '';
@@ -30,13 +30,13 @@ in
       port = mkOption {
         type = types.port;
         default = 8080;
-        description = "The port of the Xandikos web application";
+        description = lib.mdDoc "The port of the Xandikos web application";
       };
 
       routePrefix = mkOption {
         type = types.str;
         default = "/";
-        description = ''
+        description = lib.mdDoc ''
           Path to Xandikos.
           Useful when Xandikos is behind a reverse proxy.
         '';
@@ -52,14 +52,14 @@ in
             "--dump-dav-xml"
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra command line arguments to pass to xandikos.
         '';
       };
 
       nginx = mkOption {
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Configuration for nginx reverse proxy.
         '';
 
@@ -68,14 +68,14 @@ in
             enable = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Configure the nginx reverse proxy settings.
               '';
             };
 
             hostName = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The hostname use to setup the virtualhost configuration
               '';
             };
diff --git a/nixos/modules/services/networking/xinetd.nix b/nixos/modules/services/networking/xinetd.nix
index 2f527ab156aa..b9120f37ba24 100644
--- a/nixos/modules/services/networking/xinetd.nix
+++ b/nixos/modules/services/networking/xinetd.nix
@@ -44,19 +44,19 @@ 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 = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Additional configuration lines added to the default section of xinetd's configuration.
       '';
     };
 
     services.xinetd.services = mkOption {
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         A list of services provided by xinetd.
       '';
 
@@ -67,53 +67,53 @@ in
           name = mkOption {
             type = types.str;
             example = "login";
-            description = "Name of the service.";
+            description = lib.mdDoc "Name of the service.";
           };
 
           protocol = mkOption {
             type = types.str;
             default = "tcp";
             description =
-              "Protocol of the service.  Usually <literal>tcp</literal> or <literal>udp</literal>.";
+              lib.mdDoc "Protocol of the service.  Usually `tcp` or `udp`.";
           };
 
           port = mkOption {
-            type = types.int;
+            type = types.port;
             default = 0;
             example = 123;
-            description = "Port number of the service.";
+            description = lib.mdDoc "Port number of the service.";
           };
 
           user = mkOption {
             type = types.str;
             default = "nobody";
-            description = "User account for the service";
+            description = lib.mdDoc "User account for the service";
           };
 
           server = mkOption {
             type = types.str;
             example = "/foo/bin/ftpd";
-            description = "Path of the program that implements the service.";
+            description = lib.mdDoc "Path of the program that implements the service.";
           };
 
           serverArgs = mkOption {
             type = types.separatedString " ";
             default = "";
-            description = "Command-line arguments for the server program.";
+            description = lib.mdDoc "Command-line arguments for the server program.";
           };
 
           flags = mkOption {
             type = types.str;
             default = "";
-            description = "";
+            description = lib.mdDoc "";
           };
 
           unlisted = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether this server is listed in
-              <filename>/etc/services</filename>.  If so, the port
+              {file}`/etc/services`.  If so, the port
               number can be omitted.
             '';
           };
@@ -121,7 +121,7 @@ in
           extraConfig = mkOption {
             type = types.lines;
             default = "";
-            description = "Extra configuration-lines added to the section of the service.";
+            description = lib.mdDoc "Extra configuration-lines added to the section of the service.";
           };
 
         };
diff --git a/nixos/modules/services/networking/xl2tpd.nix b/nixos/modules/services/networking/xl2tpd.nix
index 7dbe51422d96..7d2595707612 100644
--- a/nixos/modules/services/networking/xl2tpd.nix
+++ b/nixos/modules/services/networking/xl2tpd.nix
@@ -5,29 +5,29 @@ 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;
-        description = "The server-side IP address.";
+        description = lib.mdDoc "The server-side IP address.";
         default     = "10.125.125.1";
       };
 
       clientIpRange = mkOption {
         type        = types.str;
-        description = "The range from which client IPs are drawn.";
+        description = lib.mdDoc "The range from which client IPs are drawn.";
         default     = "10.125.125.2-11";
       };
 
       extraXl2tpOptions = mkOption {
         type        = types.lines;
-        description = "Adds extra lines to the xl2tpd configuration file.";
+        description = lib.mdDoc "Adds extra lines to the xl2tpd configuration file.";
         default     = "";
       };
 
       extraPppdOptions = mkOption {
         type        = types.lines;
-        description = "Adds extra lines to the pppd options file.";
+        description = lib.mdDoc "Adds extra lines to the pppd options file.";
         default     = "";
         example     = ''
           ms-dns 8.8.8.8
@@ -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
 
@@ -116,18 +116,18 @@ with lib;
         #username	xl2tpd	password	*
         EOF
 
-        chown root.root ppp/chap-secrets
+        chown root:root ppp/chap-secrets
         chmod 600 ppp/chap-secrets
 
         # The documentation says this file should be present but doesn't explain why and things work even if not there:
         [ -f l2tp-secrets ] || (echo -n "* * "; ${pkgs.apg}/bin/apg -n 1 -m 32 -x 32 -a 1 -M LCN) > l2tp-secrets
-        chown root.root l2tp-secrets
+        chown root:root l2tp-secrets
         chmod 600 l2tp-secrets
 
         popd > /dev/null
 
         mkdir -p /run/xl2tpd
-        chown root.root /run/xl2tpd
+        chown root:root /run/xl2tpd
         chmod 700       /run/xl2tpd
       '';
 
diff --git a/nixos/modules/services/networking/xray.nix b/nixos/modules/services/networking/xray.nix
new file mode 100644
index 000000000000..e2fd83c4dfd9
--- /dev/null
+++ b/nixos/modules/services/networking/xray.nix
@@ -0,0 +1,96 @@
+{ 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}";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/xrdp.nix b/nixos/modules/services/networking/xrdp.nix
index 747fb7a1f9c4..ed7f1dadd370 100644
--- a/nixos/modules/services/networking/xrdp.nix
+++ b/nixos/modules/services/networking/xrdp.nix
@@ -42,21 +42,21 @@ 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;
         default = pkgs.xrdp;
         defaultText = literalExpression "pkgs.xrdp";
-        description = ''
+        description = lib.mdDoc ''
           The package to use for the xrdp daemon's binary.
         '';
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3389;
-        description = ''
+        description = lib.mdDoc ''
           Specifies on which port the xrdp daemon listens.
         '';
       };
@@ -64,14 +64,14 @@ in
       openFirewall = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to open the firewall for the specified RDP port.";
+        description = lib.mdDoc "Whether to open the firewall for the specified RDP port.";
       };
 
       sslKey = mkOption {
         type = types.str;
         default = "/etc/xrdp/key.pem";
         example = "/path/to/your/key.pem";
-        description = ''
+        description = lib.mdDoc ''
           ssl private key path
           A self-signed certificate will be generated if file not exists.
         '';
@@ -81,7 +81,7 @@ in
         type = types.str;
         default = "/etc/xrdp/cert.pem";
         example = "/path/to/your/cert.pem";
-        description = ''
+        description = lib.mdDoc ''
           ssl certificate path
           A self-signed certificate will be generated if file not exists.
         '';
@@ -91,7 +91,7 @@ in
         type = types.str;
         default = "xterm";
         example = "xfce4-session";
-        description = ''
+        description = lib.mdDoc ''
           The script to run when user log in, usually a window manager, e.g. "icewm", "xfce4-session"
           This is per-user overridable, if file ~/startwm.sh exists it will be used instead.
         '';
@@ -100,8 +100,8 @@ in
       confDir = mkOption {
         type = types.path;
         default = confDir;
-        defaultText = literalDocBook "generated from configuration";
-        description = "The location of the config files for xrdp.";
+        defaultText = literalMD "generated from configuration";
+        description = lib.mdDoc "The location of the config files for xrdp.";
       };
     };
   };
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index 99c18ae6919e..3d5cbdd2dc3e 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -4,16 +4,23 @@ let
   keysPath = "/var/lib/yggdrasil/keys.json";
 
   cfg = config.services.yggdrasil;
-  configProvided = cfg.config != { };
+  settingsProvided = cfg.settings != { };
   configFileProvided = cfg.configFile != null;
 
+  format = pkgs.formats.json { };
 in {
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "yggdrasil" "config" ]
+      [ "services" "yggdrasil" "settings" ])
+  ];
+
   options = with types; {
     services.yggdrasil = {
-      enable = mkEnableOption "the yggdrasil system service";
+      enable = mkEnableOption (lib.mdDoc "the yggdrasil system service");
 
-      config = mkOption {
-        type = attrs;
+      settings = mkOption {
+        type = format.type;
         default = {};
         example = {
           Peers = [
@@ -24,29 +31,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.
 
-          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 <code>nix-shell -p yggdrasil --run
-          "yggdrasil -genconf"</code> 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.
         '';
       };
 
@@ -54,31 +60,31 @@ in {
         type = nullOr path;
         default = null;
         example = "/run/keys/yggdrasil.conf";
-        description = ''
+        description = lib.mdDoc ''
           A file which contains JSON configuration for yggdrasil.
-          See the <option>config</option> option for more information.
+          See the {option}`settings` option for more information.
         '';
       };
 
       group = mkOption {
-        type = types.str;
-        default = "root";
+        type = types.nullOr types.str;
+        default = null;
         example = "wheel";
-        description = "Group to grant access to the Yggdrasil control socket.";
+        description = lib.mdDoc "Group to grant access to the Yggdrasil control socket. If `null`, only root can access the socket.";
       };
 
       openMulticastPort = mkOption {
         type = bool;
         default = false;
-        description = ''
+        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 <code>LinkLocalTCPPort</code> in your
-          yggdrasil configuration (<option>config</option> or
-          <option>configFile</option>) to a port number other than 0,
+          will also need to set `LinkLocalTCPPort` in your
+          yggdrasil configuration ({option}`settings` or
+          {option}`configFile`) to a port number other than 0,
           and then add that port to
-          <option>networking.firewall.allowedTCPPorts</option>.
+          {option}`networking.firewall.allowedTCPPorts`.
         '';
       };
 
@@ -86,7 +92,7 @@ in {
         type = listOf str;
         default = [];
         example = [ "tap*" ];
-        description = ''
+        description = lib.mdDoc ''
           Disable the DHCP client for any interface whose name matches
           any of the shell glob patterns in this list.  Use this
           option to prevent the DHCP client from broadcasting requests
@@ -100,14 +106,14 @@ in {
         type = package;
         default = pkgs.yggdrasil;
         defaultText = literalExpression "pkgs.yggdrasil";
-        description = "Yggdrasil package to use.";
+        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}.
-      '';
+      '');
 
     };
   };
@@ -132,16 +138,17 @@ in {
 
     systemd.services.yggdrasil = {
       description = "Yggdrasil Network Service";
-      bindsTo = [ "network-online.target" ];
-      after = [ "network-online.target" ];
+      after = [ "network-pre.target" ];
+      wants = [ "network.target" ];
+      before = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
       preStart =
-        (if configProvided || configFileProvided || cfg.persistentKeys then
+        (if settingsProvided || configFileProvided || cfg.persistentKeys then
           "echo "
 
-          + (lib.optionalString configProvided
-            "'${builtins.toJSON cfg.config}'")
+          + (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"
@@ -154,27 +161,16 @@ in {
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         Restart = "always";
 
-        Group = cfg.group;
+        DynamicUser = true;
+        StateDirectory = "yggdrasil";
         RuntimeDirectory = "yggdrasil";
         RuntimeDirectoryMode = "0750";
         BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile
           ++ lib.optional cfg.persistentKeys keysPath;
+        ReadWritePaths = "/run/yggdrasil";
 
-        # TODO: as of yggdrasil 0.3.8 and systemd 243, yggdrasil fails
-        # to set up the network adapter when DynamicUser is set.  See
-        # github.com/yggdrasil-network/yggdrasil-go/issues/557.  The
-        # following options are implied by DynamicUser according to
-        # the systemd.exec documentation, and can be removed if the
-        # upstream issue is fixed and DynamicUser is set to true:
-        PrivateTmp = true;
-        RemoveIPC = true;
-        NoNewPrivileges = true;
-        ProtectSystem = "strict";
-        RestrictSUIDSGID = true;
-        # End of list of options implied by DynamicUser.
-
-        AmbientCapabilities = "CAP_NET_ADMIN";
-        CapabilityBoundingSet = "CAP_NET_ADMIN";
+        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
+        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
         MemoryDenyWriteExecute = true;
         ProtectControlGroups = true;
         ProtectHome = "tmpfs";
@@ -184,8 +180,10 @@ in {
         RestrictNamespaces = true;
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @resources";
-      };
+        SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
+      } // (if (cfg.group != null) then {
+        Group = cfg.group;
+      } else {});
     };
 
     networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
diff --git a/nixos/modules/services/networking/yggdrasil.xml b/nixos/modules/services/networking/yggdrasil.xml
index a341d5d8153b..a7b8c469529a 100644
--- a/nixos/modules/services/networking/yggdrasil.xml
+++ b/nixos/modules/services/networking/yggdrasil.xml
@@ -27,10 +27,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"
@@ -58,7 +58,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 +78,7 @@ in {
   }];
 
   services.radvd = {
-    # Annouce the 300::/8 prefix to eth0.
+    # Announce the 300::/8 prefix to eth0.
     enable = true;
     config = ''
       interface eth0
diff --git a/nixos/modules/services/networking/zerobin.nix b/nixos/modules/services/networking/zerobin.nix
index 16db25d62304..9e07666f3e14 100644
--- a/nixos/modules/services/networking/zerobin.nix
+++ b/nixos/modules/services/networking/zerobin.nix
@@ -12,12 +12,12 @@ in
   {
     options = {
       services.zerobin = {
-        enable = mkEnableOption "0bin";
+        enable = mkEnableOption (lib.mdDoc "0bin");
 
         dataDir = mkOption {
           type = types.str;
           default = "/var/lib/zerobin";
-          description = ''
+          description = lib.mdDoc ''
           Path to the 0bin data directory
           '';
         };
@@ -25,7 +25,7 @@ in
         user = mkOption {
           type = types.str;
           default = "zerobin";
-          description = ''
+          description = lib.mdDoc ''
           The user 0bin should run as
           '';
         };
@@ -33,7 +33,7 @@ in
         group = mkOption {
           type = types.str;
           default = "zerobin";
-          description = ''
+          description = lib.mdDoc ''
           The group 0bin should run as
           '';
         };
@@ -42,7 +42,7 @@ in
           type = types.int;
           default = 8000;
           example = 1357;
-          description = ''
+          description = lib.mdDoc ''
           The port zerobin should listen on
           '';
         };
@@ -51,7 +51,7 @@ in
           type = types.str;
           default = "localhost";
           example = "127.0.0.1";
-          description = ''
+          description = lib.mdDoc ''
           The address zerobin should listen to
           '';
         };
@@ -65,7 +65,7 @@ in
           )
           COMPRESSED_STATIC_FILE = True
           '';
-          description = ''
+          description = lib.mdDoc ''
           Extra configuration to be appended to the 0bin config file
           (see https://0bin.readthedocs.org/en/latest/en/options.html)
           '';
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index dd83b7facc11..1f3711bd0d72 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -17,16 +17,23 @@ let
   };
 in with lib; {
   options.services.zeronet = {
-    enable = mkEnableOption "zeronet";
+    enable = mkEnableOption (lib.mdDoc "zeronet");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.zeronet;
+      defaultText = literalExpression "pkgs.zeronet";
+      description = lib.mdDoc "ZeroNet package to use";
+    };
 
     settings = mkOption {
       type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
       default = {};
       example = literalExpression "{ global.tor = enable; }";
 
-      description = ''
-        <filename>zeronet.conf</filename> configuration. Refer to
-        <link xlink:href="https://zeronet.readthedocs.io/en/latest/faq/#is-it-possible-to-use-a-configuration-file"/>
+      description = lib.mdDoc ''
+        {file}`zeronet.conf` configuration. Refer to
+        <https://zeronet.readthedocs.io/en/latest/faq/#is-it-possible-to-use-a-configuration-file>
         for details on supported values;
       '';
     };
@@ -34,7 +41,7 @@ in with lib; {
     port = mkOption {
       type = types.port;
       default = 43110;
-      description = "Optional zeronet web UI port.";
+      description = lib.mdDoc "Optional zeronet web UI port.";
     };
 
     fileserverPort = mkOption {
@@ -42,19 +49,19 @@ in with lib; {
       # read-only config file and crashes
       type = types.port;
       default = 12261;
-      description = "Zeronet fileserver port.";
+      description = lib.mdDoc "Zeronet fileserver port.";
     };
 
     tor = mkOption {
       type = types.bool;
       default = false;
-      description = "Use TOR for zeronet traffic where possible.";
+      description = lib.mdDoc "Use TOR for zeronet traffic where possible.";
     };
 
     torAlways = mkOption {
       type = types.bool;
       default = false;
-      description = "Use TOR for all zeronet traffic.";
+      description = lib.mdDoc "Use TOR for all zeronet traffic.";
     };
   };
 
@@ -72,7 +79,7 @@ in with lib; {
 
     systemd.services.zeronet = {
       description = "zeronet";
-      after = [ "network.target" (optionalString cfg.tor "tor.service") ];
+      after = [ "network.target" ] ++ optional cfg.tor "tor.service";
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
@@ -80,7 +87,7 @@ in with lib; {
         DynamicUser = true;
         StateDirectory = "zeronet";
         SupplementaryGroups = mkIf cfg.tor [ "tor" ];
-        ExecStart = "${pkgs.zeronet}/bin/zeronet --config_file ${configFile}";
+        ExecStart = "${cfg.package}/bin/zeronet --config_file ${configFile}";
       };
     };
   };
diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix
index 3bc7d3ac0db5..0d9e25cfc52c 100644
--- a/nixos/modules/services/networking/zerotierone.nix
+++ b/nixos/modules/services/networking/zerotierone.nix
@@ -6,21 +6,21 @@ 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 = [];
     example = [ "a8a2c3c10c1a68de" ];
     type = types.listOf types.str;
-    description = ''
+    description = lib.mdDoc ''
       List of ZeroTier Network IDs to join on startup
     '';
   };
 
   options.services.zerotierone.port = mkOption {
     default = 9993;
-    type = types.int;
-    description = ''
+    type = types.port;
+    description = lib.mdDoc ''
       Network port used by ZeroTier.
     '';
   };
@@ -29,7 +29,7 @@ in
     default = pkgs.zerotierone;
     defaultText = literalExpression "pkgs.zerotierone";
     type = types.package;
-    description = ''
+    description = lib.mdDoc ''
       ZeroTier One package to use.
     '';
   };
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
index a98f92d2d710..2befab373ba0 100644
--- a/nixos/modules/services/networking/znc/default.nix
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -81,13 +81,13 @@ in
 
   options = {
     services.znc = {
-      enable = mkEnableOption "ZNC";
+      enable = mkEnableOption (lib.mdDoc "ZNC");
 
       user = mkOption {
         default = "znc";
         example = "john";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The name of an existing user account to use to own the ZNC server
           process. If not specified, a default user will be created.
         '';
@@ -97,7 +97,7 @@ in
         default = defaultUser;
         example = "users";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Group to own the ZNC process.
         '';
       };
@@ -106,7 +106,7 @@ in
         default = "/var/lib/znc";
         example = "/home/john/.znc";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The state directory for ZNC. The config and the modules will be linked
           to from this directory as well.
         '';
@@ -115,10 +115,10 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to open ports in the firewall for ZNC. Does work with
           ports for listeners specified in
-          <option>services.znc.config.Listener</option>.
+          {option}`services.znc.config.Listener`.
         '';
       };
 
@@ -149,31 +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.
-          </para>
-          <para>
-          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.
-          </para>
-          <para>
-          Nix attributes called <literal>extraConfig</literal> will be inserted
+
+          Nix attributes called `extraConfig` will be inserted
           verbatim into the resulting config file.
-          </para>
-          <para>
-          If <option>services.znc.useLegacyConfig</option> is turned on, the
-          option values in <option>services.znc.confOptions.*</option> will be
+
+          If {option}`services.znc.useLegacyConfig` is turned on, the
+          option values in {option}`services.znc.confOptions.*` will be
           gracefully be applied to this option.
-          </para>
-          <para>
+
           If you intend to update the configuration through this option, be sure
-          to enable <option>services.znc.mutable</option>, otherwise none of the
+          to enable {option}`services.znc.mutable`, otherwise none of the
           changes here will be applied after the initial deploy.
         '';
       };
@@ -181,13 +177,12 @@ in
       configFile = mkOption {
         type = types.path;
         example = literalExpression "~/.znc/configs/znc.conf";
-        description = ''
+        description = lib.mdDoc ''
           Configuration file for ZNC. It is recommended to use the
-          <option>config</option> option instead.
-          </para>
-          <para>
+          {option}`config` option instead.
+
           Setting this option will override any auto-generated config file
-          through the <option>confOptions</option> or <option>config</option>
+          through the {option}`confOptions` or {option}`config`
           options.
         '';
       };
@@ -196,7 +191,7 @@ in
         type = types.listOf types.package;
         default = [ ];
         example = literalExpression "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
-        description = ''
+        description = lib.mdDoc ''
           A list of global znc module packages to add to znc.
         '';
       };
@@ -204,17 +199,15 @@ in
       mutable = mkOption {
         default = true; # TODO: Default to true when config is set, make sure to not delete the old config if present
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Indicates whether to allow the contents of the
-          <literal>dataDir</literal> directory to be changed by the user at
+          `dataDir` directory to be changed by the user at
           run-time.
-          </para>
-          <para>
+
           If enabled, modifications to the ZNC configuration after its initial
           creation are not overwritten by a NixOS rebuild. If disabled, the
           ZNC configuration is rebuilt on every NixOS rebuild.
-          </para>
-          <para>
+
           If the user wants to manage the ZNC service using the web admin
           interface, this option should be enabled.
         '';
@@ -224,7 +217,7 @@ in
         default = [ ];
         example = [ "--debug" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Extra arguments to use for executing znc.
         '';
       };
diff --git a/nixos/modules/services/networking/znc/options.nix b/nixos/modules/services/networking/znc/options.nix
index 0db051126e86..bd67ec86d513 100644
--- a/nixos/modules/services/networking/znc/options.nix
+++ b/nixos/modules/services/networking/znc/options.nix
@@ -12,15 +12,15 @@ let
       server = mkOption {
         type = types.str;
         example = "irc.libera.chat";
-        description = ''
+        description = lib.mdDoc ''
           IRC server address.
         '';
       };
 
       port = mkOption {
-        type = types.ints.u16;
+        type = types.port;
         default = 6697;
-        description = ''
+        description = lib.mdDoc ''
           IRC server port.
         '';
       };
@@ -28,7 +28,7 @@ let
       password = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           IRC server password, such as for a Slack gateway.
         '';
       };
@@ -36,7 +36,7 @@ let
       useSSL = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use SSL to connect to the IRC server.
         '';
       };
@@ -45,7 +45,7 @@ let
         type = types.listOf types.str;
         default = [ "simple_away" ];
         example = literalExpression ''[ "simple_away" "sasl" ]'';
-        description = ''
+        description = lib.mdDoc ''
           ZNC network modules to load.
         '';
       };
@@ -54,7 +54,7 @@ let
         type = types.listOf types.str;
         default = [];
         example = [ "nixos" ];
-        description = ''
+        description = lib.mdDoc ''
           IRC channels to join.
         '';
       };
@@ -62,7 +62,7 @@ let
       hasBitlbeeControlChannel = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to add the special Bitlbee operations channel.
         '';
       };
@@ -79,9 +79,9 @@ let
           JoinDelay = 0
           Nick = johntron
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra config for the network. Consider using
-          <option>services.znc.config</option> instead.
+          {option}`services.znc.config` instead.
         '';
       };
     };
@@ -97,19 +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.
-          </para>
-          <para>
+
           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.
         '';
       };
@@ -119,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.
           '';
         };
@@ -128,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.
           '';
         };
@@ -137,7 +136,7 @@ in
           default = "znc";
           example = "johntron";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The user name used to log in to the ZNC web admin interface.
           '';
         };
@@ -145,7 +144,7 @@ in
         networks = mkOption {
           default = { };
           type = with types; attrsOf (submodule networkOpts);
-          description = ''
+          description = lib.mdDoc ''
             IRC networks to connect the user to.
           '';
           example = literalExpression ''
@@ -164,7 +163,7 @@ in
           default = "znc-user";
           example = "john";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The IRC nick.
           '';
         };
@@ -178,19 +177,19 @@ 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;
-          description = ''
+          type = types.port;
+          description = lib.mdDoc ''
             Specifies the port on which to listen.
           '';
         };
@@ -198,7 +197,7 @@ in
         useSSL = mkOption {
           default = true;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Indicates whether the ZNC server should use SSL when listening on
             the specified port. A self-signed certificate will be generated.
           '';
@@ -208,7 +207,7 @@ in
           type = types.nullOr types.str;
           default = null;
           example = "/znc/";
-          description = ''
+          description = lib.mdDoc ''
             An optional URI prefix for the ZNC web interface. Can be
             used to make ZNC available behind a reverse proxy.
           '';
@@ -217,7 +216,7 @@ in
         extraZncConf = mkOption {
           default = "";
           type = types.lines;
-          description = ''
+          description = lib.mdDoc ''
             Extra config to `znc.conf` file.
           '';
         };
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 53091d8e2a0e..ae59dcc226de 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -129,15 +129,24 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable printing support through the CUPS daemon.
         '';
       };
 
+      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;
-        description = ''
+        description = lib.mdDoc ''
           If set, CUPS is socket-activated; that is,
           instead of having it permanently running as a daemon,
           systemd will start it on the first incoming connection.
@@ -148,7 +157,7 @@ in
         type = types.listOf types.str;
         default = [ "localhost:631" ];
         example = [ "*:631" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of addresses and ports on which to listen.
         '';
       };
@@ -158,7 +167,7 @@ in
         default = [ "localhost" ];
         example = [ "all" ];
         apply = concatMapStringsSep "\n" (x: "Allow ${x}");
-        description = ''
+        description = lib.mdDoc ''
           From which hosts to allow unconditional access.
         '';
       };
@@ -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.
         '';
@@ -176,7 +185,7 @@ in
       defaultShared = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether local printers are shared by default.
         '';
       };
@@ -184,7 +193,7 @@ in
       browsing = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether shared printers are advertised.
         '';
       };
@@ -192,7 +201,7 @@ in
       webInterface = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Specifies whether the web interface is enabled.
         '';
       };
@@ -201,7 +210,7 @@ in
         type = types.str;
         default = "info";
         example = "debug";
-        description = ''
+        description = lib.mdDoc ''
           Specifies the cupsd logging verbosity.
         '';
       };
@@ -209,9 +218,9 @@ in
       extraFilesConf = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra contents of the configuration file of the CUPS daemon
-          (<filename>cups-files.conf</filename>).
+          ({file}`cups-files.conf`).
         '';
       };
 
@@ -223,9 +232,9 @@ in
             BrowsePoll cups.example.com
             MaxCopies 42
           '';
-        description = ''
+        description = lib.mdDoc ''
           Extra contents of the configuration file of the CUPS daemon
-          (<filename>cupsd.conf</filename>).
+          ({file}`cupsd.conf`).
         '';
       };
 
@@ -237,9 +246,9 @@ in
             ServerName server.example.com
             Encryption Never
           '';
-        description = ''
+        description = lib.mdDoc ''
           The contents of the client configuration.
-          (<filename>client.conf</filename>)
+          ({file}`client.conf`)
         '';
       };
 
@@ -250,9 +259,9 @@ in
           ''
             BrowsePoll cups.example.com
           '';
-        description = ''
+        description = lib.mdDoc ''
           The contents of the configuration. file of the CUPS Browsed daemon
-          (<filename>cups-browsed.conf</filename>)
+          ({file}`cups-browsed.conf`)
         '';
       };
 
@@ -261,8 +270,8 @@ in
         default = ''
           Address @LOCAL
         '';
-        description = ''
-          The contents of <filename>/etc/cups/snmp.conf</filename>. See "man
+        description = lib.mdDoc ''
+          The contents of {file}`/etc/cups/snmp.conf`. See "man
           cups-snmp.conf" for a complete description.
         '';
       };
@@ -271,12 +280,12 @@ in
         type = types.listOf types.path;
         default = [];
         example = literalExpression "with pkgs; [ gutenprint hplip splix ]";
-        description = ''
+        description = lib.mdDoc ''
           CUPS drivers to use. Drivers provided by CUPS, cups-filters,
           Ghostscript and Samba are added unconditionally. If this list contains
           Gutenprint (i.e. a derivation with
-          <literal>meta.isGutenprint = true</literal>) the PPD files in
-          <filename>/var/lib/cups/ppd</filename> will be updated automatically
+          `meta.isGutenprint = true`) the PPD files in
+          {file}`/var/lib/cups/ppd` will be updated automatically
           to avoid errors due to incompatible versions.
         '';
       };
@@ -285,7 +294,7 @@ in
         type = types.path;
         default = "/tmp";
         example = "/tmp/cups";
-        description = ''
+        description = lib.mdDoc ''
           CUPSd temporary directory.
         '';
       };
@@ -343,8 +352,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}
diff --git a/nixos/modules/services/printing/ipp-usb.nix b/nixos/modules/services/printing/ipp-usb.nix
new file mode 100644
index 000000000000..8ed2ff826871
--- /dev/null
+++ b/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/nixos/modules/services/scheduling/atd.nix b/nixos/modules/services/scheduling/atd.nix
index 9bb0191ee469..235d4f348e5e 100644
--- a/nixos/modules/services/scheduling/atd.nix
+++ b/nixos/modules/services/scheduling/atd.nix
@@ -19,19 +19,19 @@ in
     services.atd.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to enable the <command>at</command> daemon, a command scheduler.
+      description = lib.mdDoc ''
+        Whether to enable the {command}`at` daemon, a command scheduler.
       '';
     };
 
     services.atd.allowEveryone = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to make <filename>/var/spool/at{jobs,spool}</filename>
+      description = lib.mdDoc ''
+        Whether to make {file}`/var/spool/at{jobs,spool}`
         writeable by everyone (and sticky).  This is normally not
-        needed since the <command>at</command> commands are
-        setuid/setgid <literal>atd</literal>.
+        needed since the {command}`at` commands are
+        setuid/setgid `atd`.
      '';
     };
 
diff --git a/nixos/modules/services/scheduling/cron.nix b/nixos/modules/services/scheduling/cron.nix
index 1fac54003cbb..6e8fe5d9d031 100644
--- a/nixos/modules/services/scheduling/cron.nix
+++ b/nixos/modules/services/scheduling/cron.nix
@@ -40,13 +40,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Vixie cron daemon.";
+        description = lib.mdDoc "Whether to enable the Vixie cron daemon.";
       };
 
       mailto = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Email address to which job output will be mailed.";
+        description = lib.mdDoc "Email address to which job output will be mailed.";
       };
 
       systemCronJobs = mkOption {
@@ -57,11 +57,11 @@ in
             "* * * * *  eelco  echo Hello World > /home/eelco/cronout"
           ]
         '';
-        description = ''
+        description = lib.mdDoc ''
           A list of Cron jobs to be appended to the system-wide
           crontab.  See the manual page for crontab for the expected
           format. If you want to get the results mailed you must setuid
-          sendmail. See <option>security.wrappers</option>
+          sendmail. See {option}`security.wrappers`
 
           If neither /var/cron/cron.deny nor /var/cron/cron.allow exist only root
           is allowed to have its own crontab file. The /var/cron/cron.deny file
@@ -76,7 +76,7 @@ in
       cronFiles = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of extra crontab files that will be read and appended to the main
           crontab file when the cron service starts.
         '';
diff --git a/nixos/modules/services/scheduling/fcron.nix b/nixos/modules/services/scheduling/fcron.nix
index acaa995f7395..f1d2f462a755 100644
--- a/nixos/modules/services/scheduling/fcron.nix
+++ b/nixos/modules/services/scheduling/fcron.nix
@@ -40,40 +40,40 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the <command>fcron</command> daemon.";
+        description = lib.mdDoc "Whether to enable the {command}`fcron` daemon.";
       };
 
       allow = mkOption {
         type = types.listOf types.str;
         default = [ "all" ];
-        description = ''
+        description = lib.mdDoc ''
           Users allowed to use fcrontab and fcrondyn (one name per
-          line, <literal>all</literal> for everyone).
+          line, `all` for everyone).
         '';
       };
 
       deny = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Users forbidden from using fcron.";
+        description = lib.mdDoc "Users forbidden from using fcron.";
       };
 
       maxSerialJobs = mkOption {
         type = types.int;
         default = 1;
-        description = "Maximum number of serial jobs which can run simultaneously.";
+        description = lib.mdDoc "Maximum number of serial jobs which can run simultaneously.";
       };
 
       queuelen = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = "Number of jobs the serial queue and the lavg queue can contain.";
+        description = lib.mdDoc "Number of jobs the serial queue and the lavg queue can contain.";
       };
 
       systab = mkOption {
         type = types.lines;
         default = "";
-        description = ''The "system" crontab contents.'';
+        description = lib.mdDoc ''The "system" crontab contents.'';
       };
     };
 
diff --git a/nixos/modules/services/search/elasticsearch-curator.nix b/nixos/modules/services/search/elasticsearch-curator.nix
index bb2612322bba..0a21d705ef87 100644
--- a/nixos/modules/services/search/elasticsearch-curator.nix
+++ b/nixos/modules/services/search/elasticsearch-curator.nix
@@ -37,24 +37,24 @@ in {
 
   options.services.elasticsearch-curator = {
 
-    enable = mkEnableOption "elasticsearch curator";
+    enable = mkEnableOption (lib.mdDoc "elasticsearch curator");
     interval = mkOption {
-      description = "The frequency to run curator, a systemd.time such as 'hourly'";
+      description = lib.mdDoc "The frequency to run curator, a systemd.time such as 'hourly'";
       default = "hourly";
       type = types.str;
     };
     hosts = mkOption {
-      description = "a list of elasticsearch hosts to connect to";
+      description = lib.mdDoc "a list of elasticsearch hosts to connect to";
       type = types.listOf types.str;
       default = ["localhost"];
     };
     port = mkOption {
-      description = "the port that elasticsearch is listening on";
-      type = types.int;
+      description = lib.mdDoc "the port that elasticsearch is listening on";
+      type = types.port;
       default = 9200;
     };
     actionYAML = mkOption {
-      description = "curator action.yaml file contents, alternatively use curator-cli which takes a simple action command";
+      description = lib.mdDoc "curator action.yaml file contents, alternatively use curator-cli which takes a simple action command";
       type = types.lines;
       example = ''
         ---
diff --git a/nixos/modules/services/search/elasticsearch.nix b/nixos/modules/services/search/elasticsearch.nix
index 041d0b3c43fd..fa1627566ebe 100644
--- a/nixos/modules/services/search/elasticsearch.nix
+++ b/nixos/modules/services/search/elasticsearch.nix
@@ -45,50 +45,50 @@ in
 
   options.services.elasticsearch = {
     enable = mkOption {
-      description = "Whether to enable elasticsearch.";
+      description = lib.mdDoc "Whether to enable elasticsearch.";
       default = false;
       type = types.bool;
     };
 
     package = mkOption {
-      description = "Elasticsearch package to use.";
+      description = lib.mdDoc "Elasticsearch package to use.";
       default = pkgs.elasticsearch;
       defaultText = literalExpression "pkgs.elasticsearch";
       type = types.package;
     };
 
     listenAddress = mkOption {
-      description = "Elasticsearch listen address.";
+      description = lib.mdDoc "Elasticsearch listen address.";
       default = "127.0.0.1";
       type = types.str;
     };
 
     port = mkOption {
-      description = "Elasticsearch port to listen for HTTP traffic.";
+      description = lib.mdDoc "Elasticsearch port to listen for HTTP traffic.";
       default = 9200;
-      type = types.int;
+      type = types.port;
     };
 
     tcp_port = mkOption {
-      description = "Elasticsearch port for the node to node communication.";
+      description = lib.mdDoc "Elasticsearch port for the node to node communication.";
       default = 9300;
       type = types.int;
     };
 
     cluster_name = mkOption {
-      description = "Elasticsearch name that identifies your cluster for auto-discovery.";
+      description = lib.mdDoc "Elasticsearch name that identifies your cluster for auto-discovery.";
       default = "elasticsearch";
       type = types.str;
     };
 
     single_node = mkOption {
-      description = "Start a single-node cluster";
+      description = lib.mdDoc "Start a single-node cluster";
       default = true;
       type = types.bool;
     };
 
     extraConf = mkOption {
-      description = "Extra configuration for elasticsearch.";
+      description = lib.mdDoc "Extra configuration for elasticsearch.";
       default = "";
       type = types.str;
       example = ''
@@ -99,7 +99,7 @@ in
     };
 
     logging = mkOption {
-      description = "Elasticsearch logging configuration.";
+      description = lib.mdDoc "Elasticsearch logging configuration.";
       default = ''
         logger.action.name = org.elasticsearch.action
         logger.action.level = info
@@ -118,26 +118,26 @@ in
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/elasticsearch";
-      description = ''
+      description = lib.mdDoc ''
         Data directory for elasticsearch.
       '';
     };
 
     extraCmdLineOptions = mkOption {
-      description = "Extra command line options for the elasticsearch launcher.";
+      description = lib.mdDoc "Extra command line options for the elasticsearch launcher.";
       default = [ ];
       type = types.listOf types.str;
     };
 
     extraJavaOptions = mkOption {
-      description = "Extra command line options for Java.";
+      description = lib.mdDoc "Extra command line options for Java.";
       default = [ ];
       type = types.listOf types.str;
       example = [ "-Djava.net.preferIPv4Stack=true" ];
     };
 
     plugins = mkOption {
-      description = "Extra elasticsearch plugins";
+      description = lib.mdDoc "Extra elasticsearch plugins";
       default = [ ];
       type = types.listOf types.package;
       example = lib.literalExpression "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
@@ -145,7 +145,7 @@ in
 
     restartIfChanged  = mkOption {
       type = types.bool;
-      description = ''
+      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,
diff --git a/nixos/modules/services/search/hound.nix b/nixos/modules/services/search/hound.nix
index ef62175b0a3e..b41a2e2bae1f 100644
--- a/nixos/modules/services/search/hound.nix
+++ b/nixos/modules/services/search/hound.nix
@@ -8,7 +8,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the hound code search daemon.
         '';
       };
@@ -16,7 +16,7 @@ in {
       user = mkOption {
         default = "hound";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           User the hound daemon should execute under.
         '';
       };
@@ -24,7 +24,7 @@ in {
       group = mkOption {
         default = "hound";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Group the hound daemon should execute under.
         '';
       };
@@ -33,7 +33,7 @@ in {
         type = types.listOf types.str;
         default = [ ];
         example = [ "dialout" ];
-        description = ''
+        description = lib.mdDoc ''
           List of extra groups that the "hound" user should be a part of.
         '';
       };
@@ -41,7 +41,7 @@ in {
       home = mkOption {
         default = "/var/lib/hound";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path to use as hound's $HOME. If the default user
           "hound" is configured then this is the home of the "hound"
           user.
@@ -52,14 +52,14 @@ in {
         default = pkgs.hound;
         defaultText = literalExpression "pkgs.hound";
         type = types.package;
-        description = ''
+        description = lib.mdDoc ''
           Package for running hound.
         '';
       };
 
       config = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The full configuration of the Hound daemon. Note the dbpath
           should be an absolute path to a writable location on disk.
         '';
@@ -82,7 +82,7 @@ in {
         type = types.str;
         default = "0.0.0.0:6080";
         example = "127.0.0.1:6080 or just :6080";
-        description = ''
+        description = lib.mdDoc ''
           Listen on this IP:port / :port
         '';
       };
@@ -120,7 +120,6 @@ in {
                     " -conf ${pkgs.writeText "hound.json" cfg.config}";
 
       };
-      path = [ pkgs.git pkgs.mercurial pkgs.openssh ];
     };
   };
 
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
index e4ab85be9ef1..5eb2381d5d39 100644
--- a/nixos/modules/services/search/kibana.nix
+++ b/nixos/modules/services/search/kibana.nix
@@ -32,53 +32,53 @@ let
 
 in {
   options.services.kibana = {
-    enable = mkEnableOption "kibana service";
+    enable = mkEnableOption (lib.mdDoc "kibana service");
 
     listenAddress = mkOption {
-      description = "Kibana listening host";
+      description = lib.mdDoc "Kibana listening host";
       default = "127.0.0.1";
       type = types.str;
     };
 
     port = mkOption {
-      description = "Kibana listening port";
+      description = lib.mdDoc "Kibana listening port";
       default = 5601;
-      type = types.int;
+      type = types.port;
     };
 
     cert = mkOption {
-      description = "Kibana ssl certificate.";
+      description = lib.mdDoc "Kibana ssl certificate.";
       default = null;
       type = types.nullOr types.path;
     };
 
     key = mkOption {
-      description = "Kibana ssl key.";
+      description = lib.mdDoc "Kibana ssl key.";
       default = null;
       type = types.nullOr types.path;
     };
 
     index = mkOption {
-      description = "Elasticsearch index to use for saving kibana config.";
+      description = lib.mdDoc "Elasticsearch index to use for saving kibana config.";
       default = ".kibana";
       type = types.str;
     };
 
     defaultAppId = mkOption {
-      description = "Elasticsearch default application id.";
+      description = lib.mdDoc "Elasticsearch default application id.";
       default = "discover";
       type = types.str;
     };
 
     elasticsearch = {
       url = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Elasticsearch url.
 
-          Defaults to <literal>"http://localhost:9200"</literal>.
+          Defaults to `"http://localhost:9200"`.
 
           Don't set this when using Kibana >= 7.0.0 because it will result in a
-          configuration error. Use <option>services.kibana.elasticsearch.hosts</option>
+          configuration error. Use {option}`services.kibana.elasticsearch.hosts`
           instead.
         '';
         default = null;
@@ -86,11 +86,11 @@ in {
       };
 
       hosts = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           The URLs of the Elasticsearch instances to use for all your queries.
           All nodes listed here must be on the same cluster.
 
-          Defaults to <literal>[ "http://localhost:9200" ]</literal>.
+          Defaults to `[ "http://localhost:9200" ]`.
 
           This option is only valid when using kibana >= 6.6.
         '';
@@ -99,22 +99,22 @@ in {
       };
 
       username = mkOption {
-        description = "Username for elasticsearch basic auth.";
+        description = lib.mdDoc "Username for elasticsearch basic auth.";
         default = null;
         type = types.nullOr types.str;
       };
 
       password = mkOption {
-        description = "Password for elasticsearch basic auth.";
+        description = lib.mdDoc "Password for elasticsearch basic auth.";
         default = null;
         type = types.nullOr types.str;
       };
 
       ca = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           CA file to auth against elasticsearch.
 
-          It's recommended to use the <option>certificateAuthorities</option> option
+          It's recommended to use the {option}`certificateAuthorities` option
           when using kibana-5.4 or newer.
         '';
         default = null;
@@ -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 ''
@@ -138,33 +138,33 @@ in {
       };
 
       cert = mkOption {
-        description = "Certificate file to auth against elasticsearch.";
+        description = lib.mdDoc "Certificate file to auth against elasticsearch.";
         default = null;
         type = types.nullOr types.path;
       };
 
       key = mkOption {
-        description = "Key file to auth against elasticsearch.";
+        description = lib.mdDoc "Key file to auth against elasticsearch.";
         default = null;
         type = types.nullOr types.path;
       };
     };
 
     package = mkOption {
-      description = "Kibana package to use";
+      description = lib.mdDoc "Kibana package to use";
       default = pkgs.kibana;
       defaultText = literalExpression "pkgs.kibana";
       type = types.package;
     };
 
     dataDir = mkOption {
-      description = "Kibana data directory";
+      description = lib.mdDoc "Kibana data directory";
       default = "/var/lib/kibana";
       type = types.path;
     };
 
     extraConf = mkOption {
-      description = "Kibana extra configuration";
+      description = lib.mdDoc "Kibana extra configuration";
       default = {};
       type = types.attrs;
     };
diff --git a/nixos/modules/services/search/meilisearch.nix b/nixos/modules/services/search/meilisearch.nix
index f6210f6f16e1..3983b1b2c92c 100644
--- a/nixos/modules/services/search/meilisearch.nix
+++ b/nixos/modules/services/search/meilisearch.nix
@@ -16,36 +16,36 @@ in
   ###### interface
 
   options.services.meilisearch = {
-    enable = mkEnableOption "MeiliSearch - a RESTful search API";
+    enable = mkEnableOption (lib.mdDoc "MeiliSearch - a RESTful search API");
 
     package = mkOption {
-      description = "The package to use for meilisearch. Use this if you require specific features to be enabled. The default package has no features.";
+      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;
     };
 
     listenAddress = mkOption {
-      description = "MeiliSearch listen address.";
+      description = lib.mdDoc "MeiliSearch listen address.";
       default = "127.0.0.1";
       type = types.str;
     };
 
     listenPort = mkOption {
-      description = "MeiliSearch port to listen on.";
+      description = lib.mdDoc "MeiliSearch port to listen on.";
       default = 7700;
       type = types.port;
     };
 
     environment = mkOption {
-      description = "Defines the running environment of MeiliSearch.";
+      description = lib.mdDoc "Defines the running environment of MeiliSearch.";
       default = "development";
       type = types.enum [ "development" "production" ];
     };
 
     # TODO change this to LoadCredentials once possible
     masterKeyEnvironmentFile = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Path to file which contains the master key.
         By doing so, all routes will be protected and will require a key to be accessed.
         If no master key is provided, all routes can be accessed without requiring any key.
@@ -57,7 +57,7 @@ in
     };
 
     noAnalytics = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Deactivates analytics.
         Analytics allow MeiliSearch to know how many users are using MeiliSearch,
         which versions and which platforms are used.
@@ -68,7 +68,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
@@ -82,7 +82,7 @@ in
     };
 
     maxIndexSize = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Sets the maximum size of the index.
         Value must be given in bytes or explicitly stating a base unit.
         For example, the default value can be written as 107374182400, '107.7Gb', or '107374 Mb'.
@@ -93,7 +93,7 @@ in
     };
 
     payloadSizeLimit = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Sets the maximum size of accepted JSON payloads.
         Value must be given in bytes or explicitly stating a base unit.
         For example, the default value can be written as 107374182400, '107.7Gb', or '107374 Mb'.
diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix
index ea76bfc9298f..05592e9fa247 100644
--- a/nixos/modules/services/search/solr.nix
+++ b/nixos/modules/services/search/solr.nix
@@ -11,43 +11,43 @@ in
 {
   options = {
     services.solr = {
-      enable = mkEnableOption "Solr";
+      enable = mkEnableOption (lib.mdDoc "Solr");
 
       package = mkOption {
         type = types.package;
         default = pkgs.solr;
         defaultText = literalExpression "pkgs.solr";
-        description = "Which Solr package to use.";
+        description = lib.mdDoc "Which Solr package to use.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8983;
-        description = "Port on which Solr is ran.";
+        description = lib.mdDoc "Port on which Solr is ran.";
       };
 
       stateDir = mkOption {
         type = types.path;
         default = "/var/lib/solr";
-        description = "The solr home directory containing config, data, and logging files.";
+        description = lib.mdDoc "The solr home directory containing config, data, and logging files.";
       };
 
       extraJavaOptions = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Extra command line options given to the java process running Solr.";
+        description = lib.mdDoc "Extra command line options given to the java process running Solr.";
       };
 
       user = mkOption {
         type = types.str;
         default = "solr";
-        description = "User under which Solr is ran.";
+        description = lib.mdDoc "User under which Solr is ran.";
       };
 
       group = mkOption {
         type = types.str;
         default = "solr";
-        description = "Group under which Solr is ran.";
+        description = lib.mdDoc "Group under which Solr is ran.";
       };
     };
   };
diff --git a/nixos/modules/services/security/aesmd.nix b/nixos/modules/services/security/aesmd.nix
index 8268b034a15e..7b0a46d6d029 100644
--- a/nixos/modules/services/security/aesmd.nix
+++ b/nixos/modules/services/security/aesmd.nix
@@ -19,27 +19,27 @@ 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 = "Whether to build the PSW package in debug mode.";
+      description = lib.mdDoc "Whether to build the PSW package in debug mode.";
     };
     settings = mkOption {
-      description = "AESM configuration";
+      description = lib.mdDoc "AESM configuration";
       default = { };
       type = types.submodule {
         options.whitelistUrl = mkOption {
           type = with types; nullOr str;
           default = null;
           example = "http://whitelist.trustedservices.intel.com/SGX/LCWL/Linux/sgx_white_list_cert.bin";
-          description = "URL to retrieve authorized Intel SGX enclave signers.";
+          description = lib.mdDoc "URL to retrieve authorized Intel SGX enclave signers.";
         };
         options.proxy = mkOption {
           type = with types; nullOr str;
           default = null;
           example = "http://proxy_url:1234";
-          description = "HTTP network proxy.";
+          description = lib.mdDoc "HTTP network proxy.";
         };
         options.proxyType = mkOption {
           type = with types; nullOr (enum [ "default" "direct" "manual" ]);
@@ -48,18 +48,18 @@ in
             if (config.${opt.settings}.proxy != null) then "manual" else null
           '';
           example = "default";
-          description = ''
-            Type of proxy to use. The <literal>default</literal> uses the system's default proxy.
-            If <literal>direct</literal> is given, uses no proxy.
-            A value of <literal>manual</literal> uses the proxy from
-            <option>services.aesmd.settings.proxy</option>.
+          description = lib.mdDoc ''
+            Type of proxy to use. The `default` uses the system's default proxy.
+            If `direct` is given, uses no proxy.
+            A value of `manual` uses the proxy from
+            {option}`services.aesmd.settings.proxy`.
           '';
         };
         options.defaultQuotingType = mkOption {
           type = with types; nullOr (enum [ "ecdsa_256" "epid_linkable" "epid_unlinkable" ]);
           default = null;
           example = "ecdsa_256";
-          description = "Attestation quote type.";
+          description = lib.mdDoc "Attestation quote type.";
         };
       };
     };
diff --git a/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix
index d302a4e00020..ca4cf5084722 100644
--- a/nixos/modules/services/security/certmgr.nix
+++ b/nixos/modules/services/security/certmgr.nix
@@ -35,43 +35,43 @@ let
 in
 {
   options.services.certmgr = {
-    enable = mkEnableOption "certmgr";
+    enable = mkEnableOption (lib.mdDoc "certmgr");
 
     package = mkOption {
       type = types.package;
       default = pkgs.certmgr;
       defaultText = literalExpression "pkgs.certmgr";
-      description = "Which certmgr package to use in the service.";
+      description = lib.mdDoc "Which certmgr package to use in the service.";
     };
 
     defaultRemote = mkOption {
       type = types.str;
       default = "127.0.0.1:8888";
-      description = "The default CA host:port to use.";
+      description = lib.mdDoc "The default CA host:port to use.";
     };
 
     validMin = mkOption {
       default = "72h";
       type = types.str;
-      description = "The interval before a certificate expires to start attempting to renew it.";
+      description = lib.mdDoc "The interval before a certificate expires to start attempting to renew it.";
     };
 
     renewInterval = mkOption {
       default = "30m";
       type = types.str;
-      description = "How often to check certificate expirations and how often to update the cert_next_expires metric.";
+      description = lib.mdDoc "How often to check certificate expirations and how often to update the cert_next_expires metric.";
     };
 
     metricsAddress = mkOption {
       default = "127.0.0.1";
       type = types.str;
-      description = "The address for the Prometheus HTTP endpoint.";
+      description = lib.mdDoc "The address for the Prometheus HTTP endpoint.";
     };
 
     metricsPort = mkOption {
       default = 9488;
       type = types.ints.u16;
-      description = "The port for the Prometheus HTTP endpoint.";
+      description = lib.mdDoc "The port for the Prometheus HTTP endpoint.";
     };
 
     specs = mkOption {
@@ -118,40 +118,40 @@ 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.";
           };
         };
     }));
-      description = ''
+      description = lib.mdDoc ''
         Certificate specs as described by:
-        <link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" />
+        <https://github.com/cloudflare/certmgr#certificate-specs>
         These will be added to the Nix store, so they will be world readable.
       '';
     };
@@ -159,11 +159,11 @@ in
     svcManager = mkOption {
       default = "systemd";
       type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ];
-      description = ''
+      description = lib.mdDoc ''
         This specifies the service manager to use for restarting or reloading services.
-        See: <link xlink:href="https://github.com/cloudflare/certmgr#certmgryaml" />.
+        See: <https://github.com/cloudflare/certmgr#certmgryaml>.
         For how to use the "command" service manager in particular,
-        see: <link xlink:href="https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it" />.
+        see: <https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it>.
       '';
     };
 
diff --git a/nixos/modules/services/security/cfssl.nix b/nixos/modules/services/security/cfssl.nix
index 6df2343b84d2..202db98e222c 100644
--- a/nixos/modules/services/security/cfssl.nix
+++ b/nixos/modules/services/security/cfssl.nix
@@ -6,69 +6,69 @@ 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.
+        :::
       '';
     };
 
     address = mkOption {
       default = "127.0.0.1";
       type = types.str;
-      description = "Address to bind.";
+      description = lib.mdDoc "Address to bind.";
     };
 
     port = mkOption {
       default = 8888;
       type = types.port;
-      description = "Port to bind.";
+      description = lib.mdDoc "Port to bind.";
     };
 
     ca = mkOption {
       defaultText = literalExpression ''"''${cfg.dataDir}/ca.pem"'';
       type = types.str;
-      description = "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
+      description = lib.mdDoc "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
     };
 
     caKey = mkOption {
       defaultText = literalExpression ''"file:''${cfg.dataDir}/ca-key.pem"'';
       type = types.str;
-      description = "CA private key -- accepts '[file:]fname' or 'env:varname'.";
+      description = lib.mdDoc "CA private key -- accepts '[file:]fname' or 'env:varname'.";
     };
 
     caBundle = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Path to root certificate store.";
+      description = lib.mdDoc "Path to root certificate store.";
     };
 
     intBundle = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Path to intermediate certificate store.";
+      description = lib.mdDoc "Path to intermediate certificate store.";
     };
 
     intDir = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Intermediates directory.";
+      description = lib.mdDoc "Intermediates directory.";
     };
 
     metadata = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = ''
+      description = lib.mdDoc ''
         Metadata file for root certificate presence.
         The content of the file is a json dictionary (k,v): each key k is
         a SHA-1 digest of a root certificate while value v is a list of key
@@ -79,79 +79,79 @@ in {
     remote = mkOption {
       default = null;
       type = types.nullOr types.str;
-      description = "Remote CFSSL server.";
+      description = lib.mdDoc "Remote CFSSL server.";
     };
 
     configFile = mkOption {
       default = null;
       type = types.nullOr types.str;
-      description = "Path to configuration file. Do not put this in nix-store as it might contain secrets.";
+      description = lib.mdDoc "Path to configuration file. Do not put this in nix-store as it might contain secrets.";
     };
 
     responder = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Certificate for OCSP responder.";
+      description = lib.mdDoc "Certificate for OCSP responder.";
     };
 
     responderKey = mkOption {
       default = null;
       type = types.nullOr types.str;
-      description = "Private key for OCSP responder certificate. Do not put this in nix-store.";
+      description = lib.mdDoc "Private key for OCSP responder certificate. Do not put this in nix-store.";
     };
 
     tlsKey = mkOption {
       default = null;
       type = types.nullOr types.str;
-      description = "Other endpoint's CA private key. Do not put this in nix-store.";
+      description = lib.mdDoc "Other endpoint's CA private key. Do not put this in nix-store.";
     };
 
     tlsCert = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Other endpoint's CA to set up TLS protocol.";
+      description = lib.mdDoc "Other endpoint's CA to set up TLS protocol.";
     };
 
     mutualTlsCa = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Mutual TLS - require clients be signed by this CA.";
+      description = lib.mdDoc "Mutual TLS - require clients be signed by this CA.";
     };
 
     mutualTlsCn = mkOption {
       default = null;
       type = types.nullOr types.str;
-      description = "Mutual TLS - regex for whitelist of allowed client CNs.";
+      description = lib.mdDoc "Mutual TLS - regex for whitelist of allowed client CNs.";
     };
 
     tlsRemoteCa = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "CAs to trust for remote TLS requests.";
+      description = lib.mdDoc "CAs to trust for remote TLS requests.";
     };
 
     mutualTlsClientCert = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Mutual TLS - client certificate to call remote instance requiring client certs.";
+      description = lib.mdDoc "Mutual TLS - client certificate to call remote instance requiring client certs.";
     };
 
     mutualTlsClientKey = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Mutual TLS - client key to call remote instance requiring client certs. Do not put this in nix-store.";
+      description = lib.mdDoc "Mutual TLS - client key to call remote instance requiring client certs. Do not put this in nix-store.";
     };
 
     dbConfig = mkOption {
       default = null;
       type = types.nullOr types.path;
-      description = "Certificate db configuration file. Path must be writeable.";
+      description = lib.mdDoc "Certificate db configuration file. Path must be writeable.";
     };
 
     logLevel = mkOption {
       default = 1;
       type = types.enum [ 0 1 2 3 4 5 ];
-      description = "Log level (0 = DEBUG, 5 = FATAL).";
+      description = lib.mdDoc "Log level (0 = DEBUG, 5 = FATAL).";
     };
   };
 
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index 95a0ad8770e2..34897a9ac7db 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -26,24 +26,24 @@ 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) ]);
           default = { };
-          description = ''
-            ClamAV configuration. Refer to <link xlink:href="https://linux.die.net/man/5/clamd.conf"/>,
+          description = lib.mdDoc ''
+            ClamAV configuration. Refer to <https://linux.die.net/man/5/clamd.conf>,
             for details on supported values.
           '';
         };
       };
       updater = {
-        enable = mkEnableOption "ClamAV freshclam updater";
+        enable = mkEnableOption (lib.mdDoc "ClamAV freshclam updater");
 
         frequency = mkOption {
           type = types.int;
           default = 12;
-          description = ''
+          description = lib.mdDoc ''
             Number of database checks per day.
           '';
         };
@@ -51,7 +51,7 @@ in
         interval = mkOption {
           type = types.str;
           default = "hourly";
-          description = ''
+          description = lib.mdDoc ''
             How often freshclam is invoked. See systemd.time(7) for more
             information about the format.
           '';
@@ -60,8 +60,8 @@ in
         settings = mkOption {
           type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
           default = { };
-          description = ''
-            freshclam configuration. Refer to <link xlink:href="https://linux.die.net/man/5/freshclam.conf"/>,
+          description = lib.mdDoc ''
+            freshclam configuration. Refer to <https://linux.die.net/man/5/freshclam.conf>,
             for details on supported values.
           '';
         };
diff --git a/nixos/modules/services/security/endlessh-go.nix b/nixos/modules/services/security/endlessh-go.nix
new file mode 100644
index 000000000000..6557ec953cd8
--- /dev/null
+++ b/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/nixos/modules/services/security/endlessh.nix b/nixos/modules/services/security/endlessh.nix
new file mode 100644
index 000000000000..e99b4dadcd58
--- /dev/null
+++ b/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/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix
index 67e1026dcef4..3b124a4f0e08 100644
--- a/nixos/modules/services/security/fail2ban.nix
+++ b/nixos/modules/services/security/fail2ban.nix
@@ -45,10 +45,10 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the fail2ban service.
 
-          See the documentation of <option>services.fail2ban.jails</option>
+          See the documentation of {option}`services.fail2ban.jails`
           for what jails are enabled by default.
         '';
       };
@@ -58,7 +58,7 @@ in
         defaultText = literalExpression "pkgs.fail2ban";
         type = types.package;
         example = literalExpression "pkgs.fail2ban_0_11";
-        description = "The fail2ban package to use for running the fail2ban service.";
+        description = lib.mdDoc "The fail2ban package to use for running the fail2ban service.";
       };
 
       packageFirewall = mkOption {
@@ -66,14 +66,14 @@ in
         defaultText = literalExpression "pkgs.iptables";
         type = types.package;
         example = literalExpression "pkgs.nftables";
-        description = "The firewall package used by fail2ban service.";
+        description = lib.mdDoc "The firewall package used by fail2ban service.";
       };
 
       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.
         '';
@@ -82,17 +82,18 @@ in
       maxretry = mkOption {
         default = 3;
         type = types.ints.unsigned;
-        description = "Number of failures before a host gets banned.";
+        description = lib.mdDoc "Number of failures before a host gets banned.";
       };
 
       banaction = mkOption {
         default = "iptables-multiport";
         type = types.str;
         example = "nftables-multiport";
-        description = ''
+        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
         '';
       };
 
@@ -100,7 +101,7 @@ in
         default = "iptables-allport";
         type = types.str;
         example = "nftables-allport";
-        description = ''
+        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
@@ -110,7 +111,7 @@ in
       bantime-increment.enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        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...
         '';
@@ -120,7 +121,7 @@ in
         default = "4m";
         type = types.str;
         example = "8m";
-        description = ''
+        description = lib.mdDoc ''
           "bantime-increment.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
         '';
@@ -130,7 +131,7 @@ in
         default = "10h";
         type = types.str;
         example = "48h";
-        description = ''
+        description = lib.mdDoc ''
           "bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
         '';
       };
@@ -139,7 +140,7 @@ in
         default = "1";
         type = types.str;
         example = "4";
-        description = ''
+        description = lib.mdDoc ''
           "bantime-increment.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 ...
         '';
@@ -149,7 +150,7 @@ in
         default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor";
         type = types.str;
         example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
-        description = ''
+        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...
         '';
@@ -159,8 +160,8 @@ in
         default = "1 2 4 8 16 32 64";
         type = types.str;
         example = "2 4 16 128";
-        description = ''
-          "bantime-increment.multipliers" used to calculate next value of ban time instead of formula, coresponding
+        description = lib.mdDoc ''
+          "bantime-increment.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
@@ -171,9 +172,9 @@ in
         default = false;
         type = types.bool;
         example = true;
-        description = ''
+        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
+          cross over all jails, if false (default), only current jail of the ban IP will be searched
         '';
       };
 
@@ -181,7 +182,7 @@ in
         default = [ ];
         type = types.listOf types.str;
         example = [ "192.168.0.0/16" "2001:DB8::42" ];
-        description = ''
+        description = lib.mdDoc ''
           "ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which
           matches an address in this list. Several addresses can be defined using space (and/or comma) separator.
         '';
@@ -196,7 +197,7 @@ in
           dbfile    = /var/lib/fail2ban/fail2ban.sqlite3
         '';
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           The contents of Fail2ban's main configuration file.  It's
           generally not necessary to change it.
        '';
@@ -212,29 +213,37 @@ 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;
-        description = ''
+        description = lib.mdDoc ''
           The configuration of each Fail2ban “jail”.  A jail
           consists of an action (such as blocking a port using
-          <command>iptables</command>) that is triggered when a
+          {command}`iptables`) that is triggered when a
           filter applied to a log file triggers more than a certain
           number of times in a certain time period.  Actions are
-          defined in <filename>/etc/fail2ban/action.d</filename>,
+          defined in {file}`/etc/fail2ban/action.d`,
           while filters are defined in
-          <filename>/etc/fail2ban/filter.d</filename>.
+          {file}`/etc/fail2ban/filter.d`.
 
-          NixOS comes with a default <literal>sshd</literal> jail;
+          NixOS comes with a default `sshd` jail;
           for it to work well,
-          <option>services.openssh.logLevel</option> should be set to
-          <literal>"VERBOSE"</literal> or higher so that fail2ban
+          {option}`services.openssh.logLevel` should be set to
+          `"VERBOSE"` or higher so that fail2ban
           can observe failed login attempts.
-          This module sets it to <literal>"VERBOSE"</literal> if
+          This module sets it to `"VERBOSE"` if
           not set otherwise, so enabling fail2ban can make SSH logs
           more verbose.
         '';
diff --git a/nixos/modules/services/security/fprintd.nix b/nixos/modules/services/security/fprintd.nix
index 87c3f1f6f9e4..28f9b5908b53 100644
--- a/nixos/modules/services/security/fprintd.nix
+++ b/nixos/modules/services/security/fprintd.nix
@@ -18,25 +18,25 @@ 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;
         default = fprintdPkg;
         defaultText = literalExpression "if config.services.fprintd.tod.enable then pkgs.fprintd-tod else pkgs.fprintd";
-        description = ''
+        description = lib.mdDoc ''
           fprintd package to use.
         '';
       };
 
       tod = {
 
-        enable = mkEnableOption "Touch OEM Drivers library support";
+        enable = mkEnableOption (lib.mdDoc "Touch OEM Drivers library support");
 
         driver = mkOption {
           type = types.package;
           example = literalExpression "pkgs.libfprint-2-tod1-goodix";
-          description = ''
+          description = lib.mdDoc ''
             Touch OEM Drivers (TOD) package to use.
           '';
         };
diff --git a/nixos/modules/services/security/haka.nix b/nixos/modules/services/security/haka.nix
index 2cfc05f3033b..c93638f44d60 100644
--- a/nixos/modules/services/security/haka.nix
+++ b/nixos/modules/services/security/haka.nix
@@ -55,22 +55,22 @@ 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 {
         default = "empty.lua";
         example = "/srv/haka/myfilter.lua";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specify which configuration file Haka uses.
           It can be absolute path or a path relative to the sample directory of
           the haka git repo.
@@ -81,7 +81,7 @@ in
         default = [ "eth0" ];
         example = [ "any" ];
         type = with types; listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Specify which interface(s) Haka listens to.
           Use 'any' to listen to all interfaces.
         '';
@@ -91,7 +91,7 @@ in
         default = 0;
         example = 4;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           The number of threads that will be used.
           All system threads are used by default.
         '';
@@ -100,24 +100,24 @@ in
       pcap = mkOption {
         default = true;
         type = types.bool;
-        description = "Whether to enable pcap";
+        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";
         type = types.path;
-        description = "Path to file where incoming packets are dumped";
+        description = lib.mdDoc "Path to file where incoming packets are dumped";
       };
 
       dump.output  = mkOption {
         default = "/tmp/output.pcap";
         example = "/path/to/file.pcap";
         type = types.path;
-        description = "Path to file where outgoing packets are dumped";
+        description = lib.mdDoc "Path to file where outgoing packets are dumped";
       };
     };
   };
diff --git a/nixos/modules/services/security/haveged.nix b/nixos/modules/services/security/haveged.nix
index 57cef7e44d50..db12a28a7d0b 100644
--- a/nixos/modules/services/security/haveged.nix
+++ b/nixos/modules/services/security/haveged.nix
@@ -15,16 +15,16 @@ 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 {
         type = types.int;
         default = 1024;
-        description = ''
+        description = lib.mdDoc ''
           The number of bits of available entropy beneath which
           haveged should refill the entropy pool.
         '';
diff --git a/nixos/modules/services/security/hockeypuck.nix b/nixos/modules/services/security/hockeypuck.nix
index d0e152934f50..127134bc5dba 100644
--- a/nixos/modules/services/security/hockeypuck.nix
+++ b/nixos/modules/services/security/hockeypuck.nix
@@ -7,12 +7,12 @@ 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;
       type = lib.types.port;
-      description = "HKP port to listen on.";
+      description = lib.mdDoc "HKP port to listen on.";
     };
 
     settings = lib.mkOption {
@@ -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/nixos/modules/services/security/hologram-agent.nix b/nixos/modules/services/security/hologram-agent.nix
index e29267e50003..666d95b9b94a 100644
--- a/nixos/modules/services/security/hologram-agent.nix
+++ b/nixos/modules/services/security/hologram-agent.nix
@@ -14,19 +14,19 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Hologram agent for AWS instance credentials";
+        description = lib.mdDoc "Whether to enable the Hologram agent for AWS instance credentials";
       };
 
       dialAddress = mkOption {
         type        = types.str;
         default     = "localhost:3100";
-        description = "Hologram server and port.";
+        description = lib.mdDoc "Hologram server and port.";
       };
 
       httpPort = mkOption {
         type        = types.str;
         default     = "80";
-        description = "Port for metadata service to listen on.";
+        description = lib.mdDoc "Port for metadata service to listen on.";
       };
 
     };
diff --git a/nixos/modules/services/security/hologram-server.nix b/nixos/modules/services/security/hologram-server.nix
index 4acf6ae0e218..e995bc79b112 100644
--- a/nixos/modules/services/security/hologram-server.nix
+++ b/nixos/modules/services/security/hologram-server.nix
@@ -33,85 +33,85 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Hologram server for AWS instance credentials";
+        description = lib.mdDoc "Whether to enable the Hologram server for AWS instance credentials";
       };
 
       listenAddress = mkOption {
         type        = types.str;
         default     = "0.0.0.0:3100";
-        description = "Address and port to listen on";
+        description = lib.mdDoc "Address and port to listen on";
       };
 
       ldapHost = mkOption {
         type        = types.str;
-        description = "Address of the LDAP server to use";
+        description = lib.mdDoc "Address of the LDAP server to use";
       };
 
       ldapInsecure = mkOption {
         type        = types.bool;
         default     = false;
-        description = "Whether to connect to LDAP over SSL or not";
+        description = lib.mdDoc "Whether to connect to LDAP over SSL or not";
       };
 
       ldapUserAttr = mkOption {
         type        = types.str;
         default     = "cn";
-        description = "The LDAP attribute for usernames";
+        description = lib.mdDoc "The LDAP attribute for usernames";
       };
 
       ldapBaseDN = mkOption {
         type        = types.str;
-        description = "The base DN for your Hologram users";
+        description = lib.mdDoc "The base DN for your Hologram users";
       };
 
       ldapBindDN = mkOption {
         type        = types.str;
-        description = "DN of account to use to query the LDAP server";
+        description = lib.mdDoc "DN of account to use to query the LDAP server";
       };
 
       ldapBindPassword = mkOption {
         type        = types.str;
-        description = "Password of account to use to query the LDAP server";
+        description = lib.mdDoc "Password of account to use to query the LDAP server";
       };
 
       enableLdapRoles = mkOption {
         type        = types.bool;
         default     = false;
-        description = "Whether to assign user roles based on the user's LDAP group memberships";
+        description = lib.mdDoc "Whether to assign user roles based on the user's LDAP group memberships";
       };
 
       groupClassAttr = mkOption {
         type = types.str;
         default = "groupOfNames";
-        description = "The objectclass attribute to search for groups when enableLdapRoles is true";
+        description = lib.mdDoc "The objectclass attribute to search for groups when enableLdapRoles is true";
       };
 
       roleAttr = mkOption {
         type        = types.str;
         default     = "businessCategory";
-        description = "Which LDAP group attribute to search for authorized role ARNs";
+        description = lib.mdDoc "Which LDAP group attribute to search for authorized role ARNs";
       };
 
       awsAccount = mkOption {
         type        = types.str;
-        description = "AWS account number";
+        description = lib.mdDoc "AWS account number";
       };
 
       awsDefaultRole = mkOption {
         type        = types.str;
-        description = "AWS default role";
+        description = lib.mdDoc "AWS default role";
       };
 
       statsAddress = mkOption {
         type        = types.str;
         default     = "";
-        description = "Address of statsd server";
+        description = lib.mdDoc "Address of statsd server";
       };
 
       cacheTimeoutSeconds = mkOption {
         type        = types.int;
         default     = 3600;
-        description = "How often (in seconds) to refresh the LDAP cache";
+        description = lib.mdDoc "How often (in seconds) to refresh the LDAP cache";
       };
     };
   };
diff --git a/nixos/modules/services/security/infnoise.nix b/nixos/modules/services/security/infnoise.nix
new file mode 100644
index 000000000000..739a0a84d90b
--- /dev/null
+++ b/nixos/modules/services/security/infnoise.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.infnoise;
+in {
+  options = {
+    services.infnoise = {
+      enable = mkEnableOption (lib.mdDoc "the Infinite Noise TRNG driver");
+
+      fillDevRandom = mkOption {
+        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
+          manually obtain randomness.
+        '';
+        type = types.bool;
+        default = true;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.infnoise ];
+
+    services.udev.extraRules = ''
+      SUBSYSTEM=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", SYMLINK+="infnoise", TAG+="systemd", GROUP="dialout", MODE="0664", ENV{SYSTEMD_WANTS}="infnoise.service"
+    '';
+
+    systemd.services.infnoise = mkIf cfg.fillDevRandom {
+      description = "Infinite Noise TRNG driver";
+
+      bindsTo = [ "dev-infnoise.device" ];
+      after = [ "dev-infnoise.device" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.infnoise}/bin/infnoise --dev-random --debug";
+        Restart = "always";
+        User = "infnoise";
+        DynamicUser = true;
+        SupplementaryGroups = [ "dialout" ];
+        DeviceAllow = [ "/dev/infnoise" ];
+        DevicePolicy = "closed";
+        PrivateNetwork = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true; # only reads entropy pool size and watermark
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix
new file mode 100644
index 000000000000..55120799c993
--- /dev/null
+++ b/nixos/modules/services/security/kanidm.nix
@@ -0,0 +1,355 @@
+{ config, lib, options, pkgs, ... }:
+let
+  cfg = config.services.kanidm;
+  settingsFormat = pkgs.formats.toml { };
+  # Remove null values, so we can document optional values that don't end up in the generated TOML file.
+  filterConfig = lib.converge (lib.filterAttrsRecursive (_: v: v != null));
+  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);
+
+  defaultServiceConfig = {
+    BindReadOnlyPaths = [
+      "/nix/store"
+      "-/etc/resolv.conf"
+      "-/etc/nsswitch.conf"
+      "-/etc/hosts"
+      "-/etc/localtime"
+    ];
+    CapabilityBoundingSet = "";
+    # ProtectClock= adds DeviceAllow=char-rtc r
+    DeviceAllow = "";
+    # Implies ProtectSystem=strict, which re-mounts all paths
+    # DynamicUser = true;
+    LockPersonality = true;
+    MemoryDenyWriteExecute = true;
+    NoNewPrivileges = true;
+    PrivateDevices = true;
+    PrivateMounts = true;
+    PrivateNetwork = true;
+    PrivateTmp = true;
+    PrivateUsers = true;
+    ProcSubset = "pid";
+    ProtectClock = true;
+    ProtectHome = true;
+    ProtectHostname = true;
+    # Would re-mount paths ignored by temporary root
+    #ProtectSystem = "strict";
+    ProtectControlGroups = true;
+    ProtectKernelLogs = true;
+    ProtectKernelModules = true;
+    ProtectKernelTunables = true;
+    ProtectProc = "invisible";
+    RestrictAddressFamilies = [ ];
+    RestrictNamespaces = true;
+    RestrictRealtime = true;
+    RestrictSUIDSGID = true;
+    SystemCallArchitectures = "native";
+    SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
+    # Does not work well with the temporary root
+    #UMask = "0066";
+  };
+
+in
+{
+  options.services.kanidm = {
+    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 {
+        freeformType = settingsFormat.type;
+
+        options = {
+          bindaddress = lib.mkOption {
+            description = lib.mdDoc "Address/port combination the webserver binds to.";
+            example = "[::1]:8443";
+            type = lib.types.str;
+          };
+          # Should be optional but toml does not accept null
+          ldapbindaddress = lib.mkOption {
+            description = lib.mdDoc ''
+              Address and port the LDAP server is bound to. Setting this to `null` disables the LDAP interface.
+            '';
+            example = "[::1]:636";
+            default = null;
+            type = lib.types.nullOr lib.types.str;
+          };
+          origin = lib.mkOption {
+            description = lib.mdDoc "The origin of your Kanidm instance. Must have https as protocol.";
+            example = "https://idm.example.org";
+            type = lib.types.strMatching "^https://.*";
+          };
+          domain = lib.mkOption {
+            description = lib.mdDoc ''
+              The `domain` that Kanidm manages. Must be below or equal to the domain
+              specified in `serverSettings.origin`.
+              This can be left at `null`, only if your instance has the role `ReadOnlyReplica`.
+              While it is possible to change the domain later on, it requires extra steps!
+              Please consider the warnings and execute the steps described
+              [in the documentation](https://kanidm.github.io/kanidm/stable/administrivia.html#rename-the-domain).
+            '';
+            example = "example.org";
+            default = null;
+            type = lib.types.nullOr lib.types.str;
+          };
+          db_path = lib.mkOption {
+            description = lib.mdDoc "Path to Kanidm database.";
+            default = "/var/lib/kanidm/kanidm.db";
+            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";
+            type = lib.types.enum [ "default" "verbose" "perfbasic" "perffull" ];
+          };
+          role = lib.mkOption {
+            description = lib.mdDoc "The role of this server. This affects the replication relationship and thereby available features.";
+            default = "WriteReplica";
+            type = lib.types.enum [ "WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica" ];
+          };
+        };
+      };
+      default = { };
+      description = lib.mdDoc ''
+        Settings for Kanidm, see
+        [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/server_configuration.md)
+        and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/server.toml)
+        for possible values.
+      '';
+    };
+
+    clientSettings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+
+        options.uri = lib.mkOption {
+          description = lib.mdDoc "Address of the Kanidm server.";
+          example = "http://127.0.0.1:8080";
+          type = lib.types.str;
+        };
+      };
+      description = lib.mdDoc ''
+        Configure Kanidm clients, needed for the PAM daemon. See
+        [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/client_tools.md#kanidm-configuration)
+        and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/config)
+        for possible values.
+      '';
+    };
+
+    unixSettings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+
+        options.pam_allowed_login_groups = lib.mkOption {
+          description = lib.mdDoc "Kanidm groups that are allowed to login using PAM.";
+          example = "my_pam_group";
+          type = lib.types.listOf lib.types.str;
+        };
+      };
+      description = lib.mdDoc ''
+        Configure Kanidm unix daemon.
+        See [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/pam_and_nsswitch.md#the-unix-daemon)
+        and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/unixd)
+        for possible values.
+      '';
+    };
+  };
+
+  config = lib.mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) {
+    assertions =
+      [
+        {
+          assertion = !cfg.enableServer || ((cfg.serverSettings.tls_chain or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_chain);
+          message = ''
+            <option>services.kanidm.serverSettings.tls_chain</option> points to
+            a file in the Nix store. You should use a quoted absolute path to
+            prevent this.
+          '';
+        }
+        {
+          assertion = !cfg.enableServer || ((cfg.serverSettings.tls_key or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_key);
+          message = ''
+            <option>services.kanidm.serverSettings.tls_key</option> points to
+            a file in the Nix store. You should use a quoted absolute path to
+            prevent this.
+          '';
+        }
+        {
+          assertion = !cfg.enableClient || options.services.kanidm.clientSettings.isDefined;
+          message = ''
+            <option>services.kanidm.clientSettings</option> needs to be configured
+            if the client is enabled.
+          '';
+        }
+        {
+          assertion = !cfg.enablePam || options.services.kanidm.clientSettings.isDefined;
+          message = ''
+            <option>services.kanidm.clientSettings</option> needs to be configured
+            for the PAM daemon to connect to the Kanidm server.
+          '';
+        }
+        {
+          assertion = !cfg.enableServer || (cfg.serverSettings.domain == null
+            -> cfg.serverSettings.role == "WriteReplica" || cfg.serverSettings.role == "WriteReplicaNoUI");
+          message = ''
+            <option>services.kanidm.serverSettings.domain</option> can only be set if this instance
+            is not a ReadOnlyReplica. Otherwise the db would inherit it from
+            the instance it follows.
+          '';
+        }
+      ];
+
+    environment.systemPackages = lib.mkIf cfg.enableClient [ pkgs.kanidm ];
+
+    systemd.services.kanidm = lib.mkIf cfg.enableServer {
+      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";
+
+        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";
+      };
+      environment.RUST_LOG = "info";
+    };
+
+    systemd.services.kanidm-unixd = lib.mkIf cfg.enablePam {
+      description = "Kanidm PAM daemon";
+      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";
+
+        BindReadOnlyPaths = [
+          "/nix/store"
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/hosts"
+          "-/etc/localtime"
+          "-/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 = false;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        TemporaryFileSystem = "/:ro";
+      };
+      environment.RUST_LOG = "info";
+    };
+
+    systemd.services.kanidm-unixd-tasks = lib.mkIf cfg.enablePam {
+      description = "Kanidm PAM home management daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "kanidm-unixd.service" ];
+      partOf = [ "kanidm-unixd.service" ];
+      restartTriggers = [ unixConfigFile clientConfigFile ];
+      serviceConfig = {
+        ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd_tasks";
+
+        BindReadOnlyPaths = [
+          "/nix/store"
+          "-/etc/resolv.conf"
+          "-/etc/nsswitch.conf"
+          "-/etc/hosts"
+          "-/etc/localtime"
+          "-/etc/kanidm"
+          "-/etc/static/kanidm"
+        ];
+        BindPaths = [
+          # To manage home directories
+          "/home"
+          # To connect to kanidm-unixd
+          "/run/kanidm-unixd:/var/run/kanidm-unixd"
+        ];
+        # CAP_DAC_OVERRIDE is needed to ignore ownership of unixd socket
+        CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_DAC_READ_SEARCH" ];
+        IPAddressDeny = "any";
+        # Need access to users
+        PrivateUsers = false;
+        # Need access to home directories
+        ProtectHome = false;
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        TemporaryFileSystem = "/:ro";
+      };
+      environment.RUST_LOG = "info";
+    };
+
+    # These paths are hardcoded
+    environment.etc = lib.mkMerge [
+      (lib.mkIf options.services.kanidm.clientSettings.isDefined {
+        "kanidm/config".source = clientConfigFile;
+      })
+      (lib.mkIf cfg.enablePam {
+        "kanidm/unixd".source = unixConfigFile;
+      })
+    ];
+
+    system.nssModules = lib.mkIf cfg.enablePam [ pkgs.kanidm ];
+
+    system.nssDatabases.group = lib.optional cfg.enablePam "kanidm";
+    system.nssDatabases.passwd = lib.optional cfg.enablePam "kanidm";
+
+    users.groups = lib.mkMerge [
+      (lib.mkIf cfg.enableServer {
+        kanidm = { };
+      })
+      (lib.mkIf cfg.enablePam {
+        kanidm-unixd = { };
+      })
+    ];
+    users.users = lib.mkMerge [
+      (lib.mkIf cfg.enableServer {
+        kanidm = {
+          description = "Kanidm server";
+          isSystemUser = true;
+          group = "kanidm";
+          packages = with pkgs; [ kanidm ];
+        };
+      })
+      (lib.mkIf cfg.enablePam {
+        kanidm-unixd = {
+          description = "Kanidm PAM daemon";
+          isSystemUser = true;
+          group = "kanidm-unixd";
+        };
+      })
+    ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ erictapen Flakebi ];
+  meta.buildDocsInSandbox = false;
+}
diff --git a/nixos/modules/services/security/munge.nix b/nixos/modules/services/security/munge.nix
index 891788864710..4d6fe33f697b 100644
--- a/nixos/modules/services/security/munge.nix
+++ b/nixos/modules/services/security/munge.nix
@@ -15,12 +15,12 @@ in
   options = {
 
     services.munge = {
-      enable = mkEnableOption "munge service";
+      enable = mkEnableOption (lib.mdDoc "munge service");
 
       password = mkOption {
         default = "/etc/munge/munge.key";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path to a daemon's secret key.
         '';
       };
diff --git a/nixos/modules/services/security/nginx-sso.nix b/nixos/modules/services/security/nginx-sso.nix
index b4de1d36edd8..971f22ed3476 100644
--- a/nixos/modules/services/security/nginx-sso.nix
+++ b/nixos/modules/services/security/nginx-sso.nix
@@ -8,13 +8,13 @@ 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;
       default = pkgs.nginx-sso;
       defaultText = literalExpression "pkgs.nginx-sso";
-      description = ''
+      description = lib.mdDoc ''
         The nginx-sso package that should be used.
       '';
     };
@@ -40,9 +40,9 @@ in {
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         nginx-sso configuration
-        (<link xlink:href="https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration">documentation</link>)
+        ([documentation](https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration))
         as a Nix attribute set.
       '';
     };
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 5c89d5872376..e3f8e75ca247 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -86,13 +86,13 @@ let
 in
 {
   options.services.oauth2_proxy = {
-    enable = mkEnableOption "oauth2_proxy";
+    enable = mkEnableOption (lib.mdDoc "oauth2_proxy");
 
     package = mkOption {
       type = types.package;
       default = pkgs.oauth2-proxy;
       defaultText = literalExpression "pkgs.oauth2-proxy";
-      description = ''
+      description = lib.mdDoc ''
         The package that provides oauth2-proxy.
       '';
     };
@@ -118,7 +118,7 @@ in
         "oidc"
       ];
       default = "google";
-      description = ''
+      description = lib.mdDoc ''
         OAuth provider.
       '';
     };
@@ -126,14 +126,14 @@ in
     approvalPrompt = mkOption {
       type = types.enum ["force" "auto"];
       default = "force";
-      description = ''
+      description = lib.mdDoc ''
         OAuth approval_prompt.
       '';
     };
 
     clientID = mkOption {
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         The OAuth Client ID.
       '';
       example = "123456.apps.googleusercontent.com";
@@ -141,7 +141,7 @@ in
 
     clientSecret = mkOption {
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         The OAuth Client Secret.
       '';
     };
@@ -149,7 +149,7 @@ in
     skipAuthRegexes = mkOption {
      type = types.listOf types.str;
      default = [];
-     description = ''
+     description = lib.mdDoc ''
        Skip authentication for requests matching any of these regular
        expressions.
      '';
@@ -160,16 +160,16 @@ 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.
         '';
       };
 
       addresses = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Line-separated email addresses that are allowed to authenticate.
         '';
       };
@@ -178,7 +178,7 @@ in
     loginURL = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Authentication endpoint.
 
         You only need to set this if you are using a self-hosted provider (e.g.
@@ -191,7 +191,7 @@ in
     redeemURL = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Token redemption endpoint.
 
         You only need to set this if you are using a self-hosted provider (e.g.
@@ -204,7 +204,7 @@ in
     validateURL = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Access token validation endpoint.
 
         You only need to set this if you are using a self-hosted provider (e.g.
@@ -219,7 +219,7 @@ in
       # doesn't require it so making it optional.
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The OAuth2 redirect URL.
       '';
       example = "https://internalapp.yourcompany.com/oauth2/callback";
@@ -229,14 +229,14 @@ in
       tenant = mkOption {
         type = types.str;
         default = "common";
-        description = ''
+        description = lib.mdDoc ''
           Go to a tenant-specific or common (tenant-independent) endpoint.
         '';
       };
 
       resource = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The resource that is protected.
         '';
       };
@@ -245,28 +245,28 @@ in
     google = {
       adminEmail = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The Google Admin to impersonate for API calls.
 
           Only users with access to the Admin APIs can access the Admin SDK
           Directory API, thus the service account needs to impersonate one of
           those users to access the Admin SDK Directory API.
 
-          See <link xlink:href="https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account" />.
+          See <https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account>.
         '';
       };
 
       groups = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Restrict logins to members of these Google groups.
         '';
       };
 
       serviceAccountJSON = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           The path to the service account JSON credentials.
         '';
       };
@@ -276,7 +276,7 @@ in
       org = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Restrict logins to members of this organisation.
         '';
       };
@@ -284,7 +284,7 @@ in
       team = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Restrict logins to members of this team.
         '';
       };
@@ -296,8 +296,8 @@ in
     upstream = mkOption {
       type = with types; coercedTo str (x: [x]) (listOf str);
       default = [];
-      description = ''
-        The http url(s) of the upstream endpoint or <literal>file://</literal>
+      description = lib.mdDoc ''
+        The http url(s) of the upstream endpoint or `file://`
         paths for static files. Routing is based on the path.
       '';
     };
@@ -305,7 +305,7 @@ in
     passAccessToken = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Pass OAuth access_token to upstream via X-Forwarded-Access-Token header.
       '';
     };
@@ -313,7 +313,7 @@ in
     passBasicAuth = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream.
       '';
     };
@@ -321,7 +321,7 @@ in
     basicAuthPassword = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The password to set when passing the HTTP Basic Auth header.
       '';
     };
@@ -329,7 +329,7 @@ in
     passHostHeader = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Pass the request Host Header to upstream.
       '';
     };
@@ -337,7 +337,7 @@ in
     signatureKey = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         GAP-Signature request signature key.
       '';
       example = "sha1:secret0";
@@ -347,7 +347,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).
@@ -358,7 +358,7 @@ in
       expire = mkOption {
         type = types.str;
         default = "168h0m0s";
-        description = ''
+        description = lib.mdDoc ''
           Expire timeframe for cookie.
         '';
       };
@@ -366,7 +366,7 @@ in
       httpOnly = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Set HttpOnly cookie flag.
         '';
       };
@@ -374,7 +374,7 @@ in
       name = mkOption {
         type = types.str;
         default = "_oauth2_proxy";
-        description = ''
+        description = lib.mdDoc ''
           The name of the cookie that the oauth_proxy creates.
         '';
       };
@@ -383,7 +383,7 @@ in
         # XXX: Unclear what the behavior is when this is not specified.
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Refresh the cookie after this duration; 0 to disable.
         '';
         example = "168h0m0s";
@@ -391,7 +391,7 @@ in
 
       secret = mkOption {
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           The seed string for secure cookies.
         '';
       };
@@ -399,7 +399,7 @@ in
       secure = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Set secure (HTTPS) cookie flag.
         '';
       };
@@ -411,10 +411,10 @@ in
     httpAddress = mkOption {
       type = types.str;
       default = "http://127.0.0.1:4180";
-      description = ''
+      description = lib.mdDoc ''
         HTTPS listening address.  This module does not expose the port by
         default. If you want this URL to be accessible to other machines, please
-        add the port to <literal>networking.firewall.allowedTCPPorts</literal>.
+        add the port to `networking.firewall.allowedTCPPorts`.
       '';
     };
 
@@ -422,16 +422,16 @@ in
       file = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Additionally authenticate against a htpasswd file. Entries must be
-          created with <literal>htpasswd -s</literal> for SHA encryption.
+          created with `htpasswd -s` for SHA encryption.
         '';
       };
 
       displayForm = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Display username / password login form if an htpasswd file is provided.
         '';
       };
@@ -440,7 +440,7 @@ in
     customTemplatesDir = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to custom HTML templates.
       '';
     };
@@ -448,9 +448,9 @@ in
     reverseProxy = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         In case when running behind a reverse proxy, controls whether headers
-        like <literal>X-Real-Ip</literal> are accepted. Usage behind a reverse
+        like `X-Real-Ip` are accepted. Usage behind a reverse
         proxy will require this flag to be set to avoid logging the reverse
         proxy IP address.
       '';
@@ -459,7 +459,7 @@ in
     proxyPrefix = mkOption {
       type = types.str;
       default = "/oauth2";
-      description = ''
+      description = lib.mdDoc ''
         The url root path that this proxy should be nested under.
       '';
     };
@@ -468,21 +468,21 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to serve over TLS.
         '';
       };
 
       certificate = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to certificate file.
         '';
       };
 
       key = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to private key file.
         '';
       };
@@ -490,11 +490,11 @@ in
       httpsAddress = mkOption {
         type = types.str;
         default = ":443";
-        description = ''
-          <literal>addr:port</literal> to listen on for HTTPS clients.
+        description = lib.mdDoc ''
+          `addr:port` to listen on for HTTPS clients.
 
-          Remember to add <literal>port</literal> to
-          <literal>allowedTCPPorts</literal> if you want other machines to be
+          Remember to add `port` to
+          `allowedTCPPorts` if you want other machines to be
           able to connect to it.
         '';
       };
@@ -503,7 +503,7 @@ in
     requestLogging = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Log requests to stdout.
       '';
     };
@@ -517,7 +517,7 @@ in
       # doesn't require it so making it optional.
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         OAuth scope specification.
       '';
     };
@@ -525,7 +525,7 @@ in
     profileURL = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Profile access endpoint.
       '';
     };
@@ -533,7 +533,7 @@ in
     setXauthrequest = mkOption {
       type = types.nullOr types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode). Setting this to 'null' means using the upstream default (false).
       '';
     };
@@ -541,7 +541,7 @@ in
     extraConfig = mkOption {
       default = {};
       type = types.attrsOf types.anything;
-      description = ''
+      description = lib.mdDoc ''
         Extra config to pass to oauth2-proxy.
       '';
     };
@@ -549,7 +549,7 @@ in
     keyFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         oauth2-proxy allows passing sensitive configuration via environment variables.
         Make a file that contains lines like
         OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com
diff --git a/nixos/modules/services/security/oauth2_proxy_nginx.nix b/nixos/modules/services/security/oauth2_proxy_nginx.nix
index 5853c5a123c6..b8e45f67cf78 100644
--- a/nixos/modules/services/security/oauth2_proxy_nginx.nix
+++ b/nixos/modules/services/security/oauth2_proxy_nginx.nix
@@ -9,14 +9,14 @@ in
       type = types.str;
       default = config.services.oauth2_proxy.httpAddress;
       defaultText = literalExpression "config.services.oauth2_proxy.httpAddress";
-      description = ''
+      description = lib.mdDoc ''
         The address of the reverse proxy endpoint for oauth2_proxy
       '';
     };
     virtualHosts = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         A list of nginx virtual hosts to put behind the oauth2 proxy
       '';
     };
diff --git a/nixos/modules/services/security/opensnitch.nix b/nixos/modules/services/security/opensnitch.nix
index f9b4985e1991..98695b1ef060 100644
--- a/nixos/modules/services/security/opensnitch.nix
+++ b/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 = ''
+                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 = ''
+                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 = ''
+              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 = ''
+              description = mdDoc ''
                 Default duration of firewall rule.
               '';
             };
 
             InterceptUnknown = mkOption {
               type = types.bool;
-              description = ''
-                Wheter to intercept spare connections.
+              description = mdDoc ''
+                Whether to intercept spare connections.
               '';
             };
 
             ProcMonitorMethod = mkOption {
               type = types.enum [ "ebpf" "proc" "ftrace" "audit" ];
-              description = ''
+              description = mdDoc ''
                 Which process monitoring method to use.
               '';
             };
 
             LogLevel = mkOption {
               type = types.enum [ 0 1 2 3 4 ];
-              description = ''
+              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 = ''
+              description = mdDoc ''
                 Which firewall backend to use.
               '';
             };
@@ -84,14 +121,14 @@ in {
 
               MaxEvents = mkOption {
                 type = types.int;
-                description = ''
+                description = mdDoc ''
                   Max events to send to the GUI.
                 '';
               };
 
               MaxStats = mkOption {
                 type = types.int;
-                description = ''
+                description = mdDoc ''
                   Max stats per item to keep in backlog.
                 '';
               };
@@ -99,9 +136,8 @@ in {
             };
           };
         };
-        description = ''
-          opensnitchd configuration. Refer to
-          <link xlink:href="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/nixos/modules/services/security/pass-secret-service.nix b/nixos/modules/services/security/pass-secret-service.nix
new file mode 100644
index 000000000000..c3c70d97ff59
--- /dev/null
+++ b/nixos/modules/services/security/pass-secret-service.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.passSecretService;
+in
+{
+  options.services.passSecretService = {
+    enable = mkEnableOption (lib.mdDoc "pass secret service");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.pass-secret-service;
+      defaultText = literalExpression "pkgs.pass-secret-service";
+      description = lib.mdDoc "Which pass-secret-service package to use.";
+      example = literalExpression "pkgs.pass-secret-service.override { python3 = pkgs.python310 }";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.packages = [ cfg.package ];
+    services.dbus.packages = [ cfg.package ];
+  };
+
+  meta.maintainers = with maintainers; [ aidalgol ];
+}
diff --git a/nixos/modules/services/security/physlock.nix b/nixos/modules/services/security/physlock.nix
index 760e80f147f7..cd7747659152 100644
--- a/nixos/modules/services/security/physlock.nix
+++ b/nixos/modules/services/security/physlock.nix
@@ -17,15 +17,15 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to enable the <command>physlock</command> screen locking mechanism.
+        description = lib.mdDoc ''
+          Whether to enable the {command}`physlock` screen locking mechanism.
 
-          Enable this and then run <command>systemctl start physlock</command>
+          Enable this and then run {command}`systemctl start physlock`
           to securely lock the screen.
 
           This will switch to a new virtual terminal, turn off console
           switching and disable SysRq mechanism (when
-          <option>services.physlock.disableSysRq</option> is set)
+          {option}`services.physlock.disableSysRq` is set)
           until the root or user password is given.
         '';
       };
@@ -33,7 +33,7 @@ in
       allowAnyUser = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow any user to lock the screen. This will install a
           setuid wrapper to allow any user to start physlock as root, which
           is a minor security risk. Call the physlock binary to use this instead
@@ -44,7 +44,7 @@ in
       disableSysRq = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to disable SysRq when locked with physlock.
         '';
       };
@@ -52,17 +52,25 @@ in
       lockMessage = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Message to show on physlock login terminal.
         '';
       };
 
+      muteKernelMessages = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Disable kernel messages on console while physlock is running.
+        '';
+      };
+
       lockOn = {
 
         suspend = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to lock screen with physlock just before suspend.
           '';
         };
@@ -70,7 +78,7 @@ in
         hibernate = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to lock screen with physlock just before hibernate.
           '';
         };
@@ -79,11 +87,11 @@ in
           type = types.listOf types.str;
           default = [];
           example = [ "display-manager.service" ];
-          description = ''
+          description = lib.mdDoc ''
             Other targets to lock the screen just before.
 
             Useful if you want to e.g. both autologin to X11 so that
-            your <filename>~/.xsession</filename> gets executed and
+            your {file}`~/.xsession` gets executed and
             still to have the screen locked so that the system can be
             booted relatively unattended.
           '';
@@ -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/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix
index b8e2d9a8b0df..e446e606cad8 100644
--- a/nixos/modules/services/security/privacyidea.nix
+++ b/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" ]; };
+  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python39; };
   python = uwsgi.python3;
   penv = python.withPackages (const [ pkgs.privacyidea ]);
   logCfg = pkgs.writeText "privacyidea-log.cfg" ''
@@ -51,37 +51,53 @@ let
     ${cfg.extraConfig}
   '';
 
+  renderValue = x:
+    if isList x then concatMapStringsSep "," (x: ''"${x}"'') x
+    else if isString x && hasInfix "," x then ''"${x}"''
+    else x;
+
+  ldapProxyConfig = pkgs.writeText "ldap-proxy.ini"
+    (generators.toINI {}
+      (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>
+          ```
         '';
       };
 
       stateDir = mkOption {
         type = types.str;
         default = "/var/lib/privacyidea";
-        description = ''
+        description = lib.mdDoc ''
           Directory where all PrivacyIDEA files will be placed by default.
         '';
       };
@@ -89,7 +105,7 @@ in
       superuserRealm = mkOption {
         type = types.listOf types.str;
         default = [ "super" "administrators" ];
-        description = ''
+        description = lib.mdDoc ''
           The realm where users are allowed to login as administrators.
         '';
       };
@@ -97,7 +113,7 @@ in
       secretKey = mkOption {
         type = types.str;
         example = "t0p s3cr3t";
-        description = ''
+        description = lib.mdDoc ''
           This is used to encrypt the auth_token.
         '';
       };
@@ -105,7 +121,7 @@ in
       pepper = mkOption {
         type = types.str;
         example = "Never know...";
-        description = ''
+        description = lib.mdDoc ''
           This is used to encrypt the admin passwords.
         '';
       };
@@ -114,7 +130,7 @@ in
         type = types.str;
         default = "${cfg.stateDir}/enckey";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/enckey"'';
-        description = ''
+        description = lib.mdDoc ''
           This is used to encrypt the token data and token passwords
         '';
       };
@@ -123,7 +139,7 @@ in
         type = types.str;
         default = "${cfg.stateDir}/private.pem";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/private.pem"'';
-        description = ''
+        description = lib.mdDoc ''
           Private Key for signing the audit log.
         '';
       };
@@ -132,26 +148,26 @@ in
         type = types.str;
         default = "${cfg.stateDir}/public.pem";
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/public.pem"'';
-        description = ''
+        description = lib.mdDoc ''
           Public key for checking signatures of the audit log.
         '';
       };
 
       adminPasswordFile = mkOption {
         type = types.path;
-        description = "File containing password for the admin user";
+        description = lib.mdDoc "File containing password for the admin user";
       };
 
       adminEmail = mkOption {
         type = types.str;
         example = "admin@example.com";
-        description = "Mail address for the admin user";
+        description = lib.mdDoc "Mail address for the admin user";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration options for pi.cfg.
         '';
       };
@@ -159,21 +175,58 @@ in
       user = mkOption {
         type = types.str;
         default = "privacyidea";
-        description = "User account under which PrivacyIDEA runs.";
+        description = lib.mdDoc "User account under which PrivacyIDEA runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "privacyidea";
-        description = "Group account under which PrivacyIDEA runs.";
+        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.path;
-          description = ''
+          type = types.nullOr types.path;
+          default = null;
+          description = lib.mdDoc ''
             Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
           '';
         };
@@ -181,13 +234,33 @@ in
         user = mkOption {
           type = types.str;
           default = "pi-ldap-proxy";
-          description = "User account under which PrivacyIDEA LDAP proxy runs.";
+          description = lib.mdDoc "User account under which PrivacyIDEA LDAP proxy runs.";
         };
 
         group = mkOption {
           type = types.str;
           default = "pi-ldap-proxy";
-          description = "Group account under which PrivacyIDEA LDAP proxy runs.";
+          description = lib.mdDoc "Group account under which PrivacyIDEA LDAP proxy runs.";
+        };
+
+        settings = mkOption {
+          type = with types; attrsOf (attrsOf (oneOf [ str bool int (listOf str) ]));
+          default = {};
+          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 [](#opt-services.privacyidea.ldap-proxy.environmentFile)
+            to inject them via `envsubst`.
+          '';
+        };
+
+        environmentFile = mkOption {
+          default = null;
+          type = types.nullOr types.str;
+          description = lib.mdDoc ''
+            Environment file containing secrets to be substituted into
+            [](#opt-services.privacyidea.ldap-proxy.settings).
+          '';
         };
       };
     };
@@ -197,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 = {
@@ -276,6 +399,18 @@ in
 
     (mkIf cfg.ldap-proxy.enable {
 
+      assertions = [
+        { assertion = let
+            xor = a: b: a && !b || !a && b;
+          in xor (cfg.ldap-proxy.settings == {}) (cfg.ldap-proxy.configFile == null);
+          message = "configFile & settings are mutually exclusive for services.privacyidea.ldap-proxy!";
+        }
+      ];
+
+      warnings = mkIf (cfg.ldap-proxy.configFile != null) [
+        "Using services.privacyidea.ldap-proxy.configFile is deprecated! Use the RFC42-style settings option instead!"
+      ];
+
       systemd.services.privacyidea-ldap-proxy = let
         ldap-proxy-env = pkgs.python3.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
       in {
@@ -284,14 +419,28 @@ in
         serviceConfig = {
           User = cfg.ldap-proxy.user;
           Group = cfg.ldap-proxy.group;
-          ExecStart = ''
+          StateDirectory = "privacyidea-ldap-proxy";
+          EnvironmentFile = mkIf (cfg.ldap-proxy.environmentFile != null)
+            [ cfg.ldap-proxy.environmentFile ];
+          ExecStartPre =
+            "${pkgs.writeShellScript "substitute-secrets-ldap-proxy" ''
+              umask 0077
+              ${pkgs.envsubst}/bin/envsubst \
+                -i ${ldapProxyConfig} \
+                -o $STATE_DIRECTORY/ldap-proxy.ini
+            ''}";
+          ExecStart = let
+            configPath = if cfg.ldap-proxy.settings != {}
+              then "%S/privacyidea-ldap-proxy/ldap-proxy.ini"
+              else cfg.ldap-proxy.configFile;
+          in ''
             ${ldap-proxy-env}/bin/twistd \
               --nodaemon \
               --pidfile= \
               -u ${cfg.ldap-proxy.user} \
               -g ${cfg.ldap-proxy.group} \
               ldap-proxy \
-              -c ${cfg.ldap-proxy.configFile}
+              -c ${configPath}
           '';
           Restart = "always";
         };
diff --git a/nixos/modules/services/security/shibboleth-sp.nix b/nixos/modules/services/security/shibboleth-sp.nix
index fea2a855e20f..e7897c3324cf 100644
--- a/nixos/modules/services/security/shibboleth-sp.nix
+++ b/nixos/modules/services/security/shibboleth-sp.nix
@@ -9,31 +9,31 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the shibboleth service";
+        description = lib.mdDoc "Whether to enable the shibboleth service";
       };
 
       configFile = mkOption {
         type = types.path;
         example = literalExpression ''"''${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml"'';
-        description = "Path to shibboleth config file";
+        description = lib.mdDoc "Path to shibboleth config file";
       };
 
       fastcgi.enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to include the shibauthorizer and shibresponder FastCGI processes";
+        description = lib.mdDoc "Whether to include the shibauthorizer and shibresponder FastCGI processes";
       };
 
       fastcgi.shibAuthorizerPort = mkOption {
         type = types.int;
         default = 9100;
-        description = "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 = "Port for shibauthorizer FastCGI proccess to bind to";
+        description = lib.mdDoc "Port for shibauthorizer FastCGI process to bind to";
       };
     };
   };
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index f4911597564b..550b61916a22 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -16,16 +16,16 @@ 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;
         defaultText = literalExpression "pkgs.sks";
         type = types.package;
-        description = "Which SKS derivation to use.";
+        description = lib.mdDoc "Which SKS derivation to use.";
       };
 
       dataDir = mkOption {
@@ -35,7 +35,7 @@ in {
         # TODO: The default might change to "/var/lib/sks" as this is more
         # common. There's also https://github.com/NixOS/nixpkgs/issues/26256
         # and "/var/db" is not FHS compliant (seems to come from BSD).
-        description = ''
+        description = lib.mdDoc ''
           Data directory (-basedir) for SKS, where the database and all
           configuration files are located (e.g. KDB, PTree, membership and
           sksconf).
@@ -45,7 +45,7 @@ in {
       extraDbConfig = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Set contents of the files "KDB/DB_CONFIG" and "PTree/DB_CONFIG" within
           the ''${dataDir} directory. This is used to configure options for the
           database for the sks key server.
@@ -59,7 +59,7 @@ in {
       hkpAddress = mkOption {
         default = [ "127.0.0.1" "::1" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Domain names, IPv4 and/or IPv6 addresses to listen on for HKP
           requests.
         '';
@@ -68,14 +68,14 @@ in {
       hkpPort = mkOption {
         default = 11371;
         type = types.ints.u16;
-        description = "HKP port to listen on.";
+        description = lib.mdDoc "HKP port to listen on.";
       };
 
       webroot = mkOption {
         type = types.nullOr types.path;
         default = "${sksPkg.webSamples}/OpenPKG";
         defaultText = literalExpression ''"''${package.webSamples}/OpenPKG"'';
-        description = ''
+        description = lib.mdDoc ''
           Source directory (will be symlinked, if not null) for the files the
           built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
           provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES".
diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix
index 53bd9efa5ac7..4e9d9571de5e 100644
--- a/nixos/modules/services/security/sshguard.nix
+++ b/nixos/modules/services/security/sshguard.nix
@@ -17,7 +17,7 @@ let
       else "sshg-fw-ipset";
   in pkgs.writeText "sshguard.conf" ''
     BACKEND="${pkgs.sshguard}/libexec/${backend}"
-    LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl ${args}"
+    LOGREADER="LANG=C ${config.systemd.package}/bin/journalctl ${args}"
   '';
 
 in {
@@ -30,13 +30,13 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "Whether to enable the sshguard service.";
+        description = lib.mdDoc "Whether to enable the sshguard service.";
       };
 
       attack_threshold = mkOption {
         default = 30;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
             Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10.
           '';
       };
@@ -45,7 +45,7 @@ in {
         default = null;
         example = 120;
         type = types.nullOr types.int;
-        description = ''
+        description = lib.mdDoc ''
             Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
           '';
       };
@@ -53,7 +53,7 @@ in {
       blacklist_file = mkOption {
         default = "/var/lib/sshguard/blacklist.db";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
             Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
           '';
       };
@@ -61,7 +61,7 @@ in {
       blocktime = mkOption {
         default = 120;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
             Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5.
 
             sshguard unblocks attacks at random intervals, so actual block times will be longer.
@@ -71,7 +71,7 @@ in {
       detection_time = mkOption {
         default = 1800;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
             Remember potential attackers for up to detection_time seconds before resetting their score.
           '';
       };
@@ -80,7 +80,7 @@ in {
         default = [ ];
         example = [ "198.51.100.56" "198.51.100.2" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
             Whitelist a list of addresses, hostnames, or address blocks.
           '';
       };
@@ -89,7 +89,7 @@ in {
         default = [ "sshd" ];
         example = [ "sshd" "exim" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
             Systemd services sshguard should receive logs of.
           '';
       };
diff --git a/nixos/modules/services/security/sslmate-agent.nix b/nixos/modules/services/security/sslmate-agent.nix
index c850eb22a031..2d72406f0db8 100644
--- a/nixos/modules/services/security/sslmate-agent.nix
+++ b/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/nixos/modules/services/security/step-ca.nix b/nixos/modules/services/security/step-ca.nix
index 95183078d7b6..433f162ecb86 100644
--- a/nixos/modules/services/security/step-ca.nix
+++ b/nixos/modules/services/security/step-ca.nix
@@ -8,68 +8,64 @@ 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;
         defaultText = lib.literalExpression "pkgs.step-ca";
-        description = "Which step-ca package to use.";
+        description = lib.mdDoc "Which step-ca package to use.";
       };
       address = lib.mkOption {
         type = lib.types.str;
         example = "127.0.0.1";
-        description = ''
+        description = lib.mdDoc ''
           The address (without port) the certificate authority should listen at.
-          This combined with <option>services.step-ca.port</option> overrides <option>services.step-ca.settings.address</option>.
+          This combined with {option}`services.step-ca.port` overrides {option}`services.step-ca.settings.address`.
         '';
       };
       port = lib.mkOption {
         type = lib.types.port;
         example = 8443;
-        description = ''
+        description = lib.mdDoc ''
           The port the certificate authority should listen on.
-          This combined with <option>services.step-ca.address</option> overrides <option>services.step-ca.settings.address</option>.
+          This combined with {option}`services.step-ca.address` overrides {option}`services.step-ca.settings.address`.
         '';
       };
       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> 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>
+        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 `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/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index a5822c02794d..2aa2964f8818 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/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,45 +224,45 @@ 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;
         default = pkgs.tor;
         defaultText = literalExpression "pkgs.tor";
-        description = "Tor package to use.";
+        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;
           default = {addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true;};
           example = {addr = "192.168.0.1"; port = 9090; IsolateDestAddr = true;};
-          description = ''
+          description = lib.mdDoc ''
             Bind to this address to listen for connections from
             Socks-speaking applications.
           '';
         };
 
         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" = {
@@ -442,62 +403,65 @@ in
           type = types.attrsOf (types.submodule ({name, config, ...}: {
             options.path = mkOption {
               type = types.path;
-              description = ''
+              description = lib.mdDoc ''
                 Path where to store the data files of the hidden service.
-                If the <option>secretKey</option> is null
-                this defaults to <literal>${stateDir}/onion/$onion</literal>,
-                otherwise to <literal>${runDir}/onion/$onion</literal>.
+                If the {option}`secretKey` is null
+                this defaults to `${stateDir}/onion/$onion`,
+                otherwise to `${runDir}/onion/$onion`.
               '';
             };
             options.secretKey = mkOption {
               type = with types; nullOr path;
               default = null;
               example = "/run/keys/tor/onion/expyuzz4wqqyqhjn/hs_ed25519_secret_key";
-              description = ''
+              description = lib.mdDoc ''
                 Secret key of the onion service.
-                If null, Tor reuses any preexisting secret key (in <option>path</option>)
+                If null, Tor reuses any preexisting secret key (in {option}`path`)
                 or generates a new one.
                 The associated public key and hostname are deterministically regenerated
                 from this file if they do not exist.
               '';
             };
             options.authorizeClient = mkOption {
-              description = descriptionGeneric "HiddenServiceAuthorizeClient";
+              description = lib.mdDoc (descriptionGeneric "HiddenServiceAuthorizeClient");
               default = null;
               type = types.nullOr (types.submodule ({...}: {
                 options = {
                   authType = mkOption {
                     type = types.enum [ "basic" "stealth" ];
-                    description = ''
-                      Either <literal>"basic"</literal> for a general-purpose authorization protocol
-                      or <literal>"stealth"</literal> for a less scalable protocol
+                    description = lib.mdDoc ''
+                      Either `"basic"` for a general-purpose authorization protocol
+                      or `"stealth"` for a less scalable protocol
                       that also hides service activity from unauthorized clients.
                     '';
                   };
                   clientNames = mkOption {
                     type = with types; nonEmptyListOf (strMatching "[A-Za-z0-9+-_]+");
-                    description = ''
+                    description = lib.mdDoc ''
                       Only clients that are listed here are authorized to access the hidden service.
-                      Generated authorization data can be found in <filename>${stateDir}/onion/$name/hostname</filename>.
+                      Generated authorization data can be found in {file}`${stateDir}/onion/$name/hostname`.
                       Clients need to put this authorization data in their configuration file using
-                      <xref linkend="opt-services.tor.settings.HidServAuth"/>.
+                      [](#opt-services.tor.settings.HidServAuth).
                     '';
                   };
                 };
               }));
             };
             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;
                 };
@@ -569,8 +534,8 @@ in
       };
 
       settings = mkOption {
-        description = ''
-          See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en">torrc manual</link>
+        description = lib.mdDoc ''
+          See [torrc manual](https://2019.www.torproject.org/docs/tor-manual.html.en)
           for documentation.
         '';
         default = {};
@@ -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,19 +674,19 @@ 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 {
                 options = {
                   onion = mkOption {
                     type = strMatching "[a-z2-7]{16}\\.onion";
-                    description = "Onion address.";
+                    description = lib.mdDoc "Onion address.";
                     example = "xxxxxxxxxxxxxxxx.onion";
                   };
                   auth = mkOption {
                     type = strMatching "[A-Za-z0-9+/]{22}";
-                    description = "Authentication cookie.";
+                    description = lib.mdDoc "Authentication cookie.";
                   };
                 };
               })
@@ -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,18 +743,18 @@ 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 = {
                 transports = mkOption {
-                  description = "List of pluggable transports.";
+                  description = lib.mdDoc "List of pluggable transports.";
                   type = listOf str;
                   example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
                 };
                 exec = mkOption {
                   type = types.str;
-                  description = "Command of pluggable transport.";
+                  description = lib.mdDoc "Command of pluggable transport.";
                 };
               };
             }));
@@ -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/nixos/modules/services/security/torify.nix b/nixos/modules/services/security/torify.nix
index 39551190dd33..4d311adebcae 100644
--- a/nixos/modules/services/security/torify.nix
+++ b/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.
+          :::
         '';
       };
 
@@ -44,7 +44,7 @@ in
         type = types.str;
         default = "localhost:9050";
         example = "192.168.0.20";
-        description = ''
+        description = lib.mdDoc ''
           IP address of TOR client to use.
         '';
       };
@@ -52,7 +52,7 @@ in
       config = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration. Contents will be added verbatim to TSocks
           configuration file.
         '';
diff --git a/nixos/modules/services/security/torsocks.nix b/nixos/modules/services/security/torsocks.nix
index fdd6ac32cc66..0647d7eb49bc 100644
--- a/nixos/modules/services/security/torsocks.nix
+++ b/nixos/modules/services/security/torsocks.nix
@@ -38,8 +38,8 @@ in
         type        = types.bool;
         default     = config.services.tor.enable && config.services.tor.client.enable;
         defaultText = literalExpression "config.services.tor.enable && config.services.tor.client.enable";
-        description = ''
-          Whether to build <literal>/etc/tor/torsocks.conf</literal>
+        description = lib.mdDoc ''
+          Whether to build `/etc/tor/torsocks.conf`
           containing the specified global torsocks configuration.
         '';
       };
@@ -48,7 +48,7 @@ in
         type    = types.str;
         default = "127.0.0.1:9050";
         example = "192.168.0.20:1234";
-        description = ''
+        description = lib.mdDoc ''
           IP/Port of the Tor SOCKS server. Currently, hostnames are
           NOT supported by torsocks.
         '';
@@ -58,7 +58,7 @@ in
         type    = types.str;
         default = "127.0.0.1:9063";
         example = "192.168.0.20:1234";
-        description = ''
+        description = lib.mdDoc ''
           IP/Port of the Tor SOCKS server for torsocks-faster wrapper suitable for HTTP.
           Currently, hostnames are NOT supported by torsocks.
         '';
@@ -67,7 +67,7 @@ in
       onionAddrRange = mkOption {
         type    = types.str;
         default = "127.42.42.0/24";
-        description = ''
+        description = lib.mdDoc ''
           Tor hidden sites do not have real IP addresses. This
           specifies what range of IP addresses will be handed to the
           application as "cookies" for .onion names.  Of course, you
@@ -81,8 +81,8 @@ in
         type    = types.nullOr types.str;
         default = null;
         example = "bob";
-        description = ''
-          SOCKS5 username. The <literal>TORSOCKS_USERNAME</literal>
+        description = lib.mdDoc ''
+          SOCKS5 username. The `TORSOCKS_USERNAME`
           environment variable overrides this option if it is set.
         '';
       };
@@ -91,8 +91,8 @@ in
         type    = types.nullOr types.str;
         default = null;
         example = "sekret";
-        description = ''
-          SOCKS5 password. The <literal>TORSOCKS_PASSWORD</literal>
+        description = lib.mdDoc ''
+          SOCKS5 password. The `TORSOCKS_PASSWORD`
           environment variable overrides this option if it is set.
         '';
       };
@@ -100,9 +100,9 @@ in
       allowInbound = mkOption {
         type    = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Set Torsocks to accept inbound connections. If set to
-          <literal>true</literal>, listen() and accept() will be
+          `true`, listen() and accept() will be
           allowed to be used with non localhost address.
         '';
       };
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
index 201b37f17ba5..1d846b194077 100644
--- a/nixos/modules/services/security/usbguard.nix
+++ b/nixos/modules/services/security/usbguard.nix
@@ -39,15 +39,15 @@ in
 
   options = {
     services.usbguard = {
-      enable = mkEnableOption "USBGuard daemon";
+      enable = mkEnableOption (lib.mdDoc "USBGuard daemon");
 
       package = mkOption {
         type = types.package;
         default = pkgs.usbguard;
         defaultText = literalExpression "pkgs.usbguard";
-        description = ''
+        description = lib.mdDoc ''
           The usbguard package to use. If you do not need the Qt GUI, use
-          <literal>pkgs.usbguard-nox</literal> to save disk space.
+          `pkgs.usbguard-nox` to save disk space.
         '';
       };
 
@@ -57,28 +57,26 @@ in
         example = ''
           allow with-interface equals { 08:*:* }
         '';
-        description = ''
+        description = lib.mdDoc ''
           The USBGuard daemon will load this as the policy rule set.
           As these rules are NixOS managed they are immutable and can't
           be changed by the IPC interface.
 
           If you do not set this option, the USBGuard daemon will load
-          it's policy rule set from <literal>${defaultRuleFile}</literal>.
+          it's policy rule set from `${defaultRuleFile}`.
           This file can be changed manually or via the IPC interface.
 
-          Running <literal>usbguard generate-policy</literal> as root will
+          Running `usbguard generate-policy` as root will
           generate a config for your currently plugged in devices.
 
-          For more details see <citerefentry>
-          <refentrytitle>usbguard-rules.conf</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry>.
+          For more details see {manpage}`usbguard-rules.conf(5)`.
         '';
       };
 
       implictPolicyTarget = mkOption {
         type = policy;
         default = "block";
-        description = ''
+        description = lib.mdDoc ''
           How to treat USB devices that don't match any rule in the policy.
           Target should be one of allow, block or reject (logically remove the
           device node from the system).
@@ -88,7 +86,7 @@ in
       presentDevicePolicy = mkOption {
         type = policy;
         default = "apply-policy";
-        description = ''
+        description = lib.mdDoc ''
           How to treat USB devices that are already connected when the daemon
           starts. Policy should be one of allow, block, reject, keep (keep
           whatever state the device is currently in) or apply-policy (evaluate
@@ -99,7 +97,7 @@ in
       presentControllerPolicy = mkOption {
         type = policy;
         default = "keep";
-        description = ''
+        description = lib.mdDoc ''
           How to treat USB controller devices that are already connected when
           the daemon starts. One of allow, block, reject, keep or apply-policy.
         '';
@@ -108,7 +106,7 @@ in
       insertedDevicePolicy = mkOption {
         type = policy;
         default = "apply-policy";
-        description = ''
+        description = lib.mdDoc ''
           How to treat USB devices that are already connected after the daemon
           starts. One of block, reject, apply-policy.
         '';
@@ -117,12 +115,12 @@ in
       restoreControllerDeviceState = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        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.
         '';
       };
 
@@ -130,7 +128,7 @@ in
         type = types.listOf types.str;
         default = [ "root" ];
         example = [ "root" "yourusername" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of usernames that the daemon will accept IPC connections from.
         '';
       };
@@ -139,7 +137,7 @@ in
         type = types.listOf types.str;
         default = [ ];
         example = [ "wheel" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of groupnames that the daemon will accept IPC connections
           from.
         '';
@@ -148,7 +146,7 @@ in
       deviceRulesWithPort = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Generate device specific rules including the "via-port" attribute.
         '';
       };
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index d48bc472cb82..7b9e31a8d990 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -7,6 +7,8 @@ let
   opt = options.services.vault;
 
   configFile = pkgs.writeText "vault.hcl" ''
+    # vault in dev mode will refuse to start if its configuration sets listener
+    ${lib.optionalString (!cfg.dev) ''
     listener "tcp" {
       address = "${cfg.address}"
       ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
@@ -17,6 +19,7 @@ let
         ''}
       ${cfg.listenerExtraConfig}
     }
+    ''}
     storage "${cfg.storageBackend}" {
       ${optionalString (cfg.storagePath   != null) ''path = "${cfg.storagePath}"''}
       ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
@@ -30,41 +33,59 @@ let
   '';
 
   allConfigPaths = [configFile] ++ cfg.extraSettingsPaths;
-
-  configOptions = escapeShellArgs (concatMap (p: ["-config" p]) allConfigPaths);
+  configOptions = escapeShellArgs
+    (lib.optional cfg.dev "-dev" ++
+     lib.optional (cfg.dev && cfg.devRootTokenID != null) "-dev-root-token-id=${cfg.devRootTokenID}"
+      ++ (concatMap (p: ["-config" p]) allConfigPaths));
 
 in
 
 {
   options = {
     services.vault = {
-      enable = mkEnableOption "Vault daemon";
+      enable = mkEnableOption (lib.mdDoc "Vault daemon");
 
       package = mkOption {
         type = types.package;
         default = pkgs.vault;
         defaultText = literalExpression "pkgs.vault";
-        description = "This option specifies the vault package to use.";
+        description = lib.mdDoc "This option specifies the vault package to use.";
+      };
+
+      dev = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          In this mode, Vault runs in-memory and starts unsealed. This option is not meant production but for development and testing i.e. for nixos tests.
+        '';
+      };
+
+      devRootTokenID = mkOption {
+        type = types.str;
+        default = false;
+        description = lib.mdDoc ''
+          Initial root token. This only applies when {option}`services.vault.dev` is true
+        '';
       };
 
       address = mkOption {
         type = types.str;
         default = "127.0.0.1:8200";
-        description = "The name of the ip interface to listen to";
+        description = lib.mdDoc "The name of the ip interface to listen to";
       };
 
       tlsCertFile = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/path/to/your/cert.pem";
-        description = "TLS certificate file. TLS will be disabled unless this option is set";
+        description = lib.mdDoc "TLS certificate file. TLS will be disabled unless this option is set";
       };
 
       tlsKeyFile = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/path/to/your/key.pem";
-        description = "TLS private key file. TLS will be disabled unless this option is set";
+        description = lib.mdDoc "TLS private key file. TLS will be disabled unless this option is set";
       };
 
       listenerExtraConfig = mkOption {
@@ -72,65 +93,65 @@ in
         default = ''
           tls_min_version = "tls12"
         '';
-        description = "Extra text appended to the listener section.";
+        description = lib.mdDoc "Extra text appended to the listener section.";
       };
 
       storageBackend = mkOption {
         type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
         default = "inmem";
-        description = "The name of the type of storage backend";
+        description = lib.mdDoc "The name of the type of storage backend";
       };
 
       storagePath = mkOption {
         type = types.nullOr types.path;
-        default = if cfg.storageBackend == "file" then "/var/lib/vault" else null;
+        default = if cfg.storageBackend == "file" || cfg.storageBackend == "raft" then "/var/lib/vault" else null;
         defaultText = literalExpression ''
-          if config.${opt.storageBackend} == "file"
+          if config.${opt.storageBackend} == "file" || cfg.storageBackend == "raft"
           then "/var/lib/vault"
           else null
         '';
-        description = "Data directory for file backend";
+        description = lib.mdDoc "Data directory for file backend";
       };
 
       storageConfig = mkOption {
         type = types.nullOr types.lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           HCL configuration to insert in the storageBackend section.
 
           Confidential values should not be specified here because this option's
           value is written to the Nix store, which is publicly readable.
           Provide credentials and such in a separate file using
-          <xref linkend="opt-services.vault.extraSettingsPaths"/>.
+          [](#opt-services.vault.extraSettingsPaths).
         '';
       };
 
       telemetryConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Telemetry configuration";
+        description = lib.mdDoc "Telemetry configuration";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra text appended to <filename>vault.hcl</filename>.";
+        description = lib.mdDoc "Extra text appended to {file}`vault.hcl`.";
       };
 
       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 = ${"''"}
@@ -143,7 +164,7 @@ in
           services.vault.extraSettingsPaths = ["/run/keys/vault.hcl"];
           services.vault.storageBackend = "postgresql";
           users.users.vault.extraGroups = ["keys"];
-          ]]></programlisting>
+          ```
         '';
       };
     };
@@ -151,11 +172,16 @@ in
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
+      {
+        assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
         message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
       }
-      { assertion = (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) && (cfg.storagePath != null -> cfg.storageBackend == "file");
-        message = ''You must set services.vault.storagePath only when using the "file" backend'';
+      {
+        assertion = (
+          (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) &&
+          (cfg.storagePath != null -> (cfg.storageBackend == "file" || cfg.storageBackend == "raft"))
+        );
+        message = ''You must set services.vault.storagePath only when using the "file" or "raft" backend'';
       }
     ];
 
@@ -186,6 +212,9 @@ in
         Group = "vault";
         ExecStart = "${cfg.package}/bin/vault server ${configOptions}";
         ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        StateDirectory = "vault";
+        # In `dev` mode vault will put its token here
+        Environment = lib.optional (cfg.dev) "HOME=/var/lib/vault";
         PrivateDevices = true;
         PrivateTmp = true;
         ProtectSystem = "full";
diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index 8277f493639c..aaa3f5507f77 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/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,12 +39,12 @@ in {
   ];
 
   options.services.vaultwarden = with types; {
-    enable = mkEnableOption "vaultwarden";
+    enable = mkEnableOption (lib.mdDoc "vaultwarden");
 
     dbBackend = mkOption {
       type = enum [ "sqlite" "mysql" "postgresql" ];
       default = "sqlite";
-      description = ''
+      description = lib.mdDoc ''
         Which database backend vaultwarden will be using.
       '';
     };
@@ -52,7 +52,7 @@ in {
     backupDir = mkOption {
       type = nullOr str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The directory under which vaultwarden will backup its persistent data.
       '';
     };
@@ -62,44 +62,94 @@ in {
       default = {};
       example = literalExpression ''
         {
-          domain = "https://bw.domain.tld:8443";
-          signupsAllowed = true;
-          rocketPort = 8222;
-          rocketLog = "critical";
+          DOMAIN = "https://bitwarden.example.com";
+          SIGNUPS_ALLOWED = false;
+
+          # Vaultwarden currently recommends running behind a reverse proxy
+          # (nginx or similar) for TLS termination, see
+          # https://github.com/dani-garcia/vaultwarden/wiki/Hardening-Guide#reverse-proxying
+          # > you should avoid enabling HTTPS via vaultwarden's built-in Rocket TLS support,
+          # > especially if your instance is publicly accessible.
+          #
+          # A suitable NixOS nginx reverse proxy example config might be:
+          #
+          #     services.nginx.virtualHosts."bitwarden.example.com" = {
+          #       enableACME = true;
+          #       forceSSL = true;
+          #       locations."/" = {
+          #         proxyPass = "http://127.0.0.1:''${toString config.services.vaultwarden.config.ROCKET_PORT}";
+          #       };
+          #     };
+          ROCKET_ADDRESS = "127.0.0.1";
+          ROCKET_PORT = 8222;
+
+          ROCKET_LOG = "critical";
+
+          # This example assumes a mailserver running on localhost,
+          # thus without transport encryption.
+          # If you use an external mail server, follow:
+          #   https://github.com/dani-garcia/vaultwarden/wiki/SMTP-configuration
+          SMTP_HOST = "127.0.0.1";
+          SMTP_PORT = 25;
+          SMTP_SSL = false;
+
+          SMTP_FROM = "admin@bitwarden.example.com";
+          SMTP_FROM_NAME = "example.com Bitwarden server";
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         The configuration of vaultwarden is done through environment variables,
-        therefore the names are converted from camel case (e.g. disable2FARemember)
-        to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+        therefore it is recommended to use upper snake case (e.g. {env}`DISABLE_2FA_REMEMBER`).
+
+        However, camel case (e.g. `disable2FARemember`) is also supported:
+        The NixOS module will convert it automatically to
+        upper case snake case (e.g. {env}`DISABLE_2FA_REMEMBER`).
         In this conversion digits (0-9) are handled just like upper case characters,
-        so foo2 would be converted to 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 FOO_2.
+        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 ()[#opt-services.vaultwarden.environmentFile) for how
+        to set up access to the Admin UI to invite initial users.
       '';
     };
 
     environmentFile = mkOption {
       type = with types; nullOr path;
       default = null;
-      example = "/root/vaultwarden.env";
-      description = ''
-        Additional environment file as defined in <citerefentry>
-        <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
-        </citerefentry>.
+      example = "/var/lib/vaultwarden.env";
+      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 {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.
+        `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:
+
+        ```
+        # Admin secret token, see
+        # https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page
+        ADMIN_TOKEN=...copy-paste a unique generated secret token here...
+        ```
       '';
     };
 
@@ -107,14 +157,14 @@ in {
       type = package;
       default = pkgs.vaultwarden;
       defaultText = literalExpression "pkgs.vaultwarden";
-      description = "Vaultwarden package to use.";
+      description = lib.mdDoc "Vaultwarden package to use.";
     };
 
     webVaultPackage = mkOption {
       type = package;
-      default = pkgs.vaultwarden-vault;
-      defaultText = literalExpression "pkgs.vaultwarden-vault";
-      description = "Web vault package to use.";
+      default = pkgs.vaultwarden.webvault;
+      defaultText = literalExpression "pkgs.vaultwarden.webvault";
+      description = lib.mdDoc "Web vault package to use.";
     };
   };
 
@@ -146,6 +196,8 @@ in {
         ProtectSystem = "strict";
         AmbientCapabilities = "CAP_NET_BIND_SERVICE";
         StateDirectory = "bitwarden_rs";
+        StateDirectoryMode = "0700";
+        Restart = "always";
       };
       wantedBy = [ "multi-user.target" ];
     };
@@ -158,6 +210,8 @@ in {
         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";
@@ -169,7 +223,7 @@ in {
     };
 
     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/nixos/modules/services/security/yubikey-agent.nix b/nixos/modules/services/security/yubikey-agent.nix
index 8be2457e1e2f..c91ff3e69a0a 100644
--- a/nixos/modules/services/security/yubikey-agent.nix
+++ b/nixos/modules/services/security/yubikey-agent.nix
@@ -21,7 +21,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to start yubikey-agent when you log in.  Also sets
           SSH_AUTH_SOCK to point at yubikey-agent.
 
@@ -34,7 +34,7 @@ in
         type = types.package;
         default = pkgs.yubikey-agent;
         defaultText = literalExpression "pkgs.yubikey-agent";
-        description = ''
+        description = lib.mdDoc ''
           The package used for the yubikey-agent daemon.
         '';
       };
diff --git a/nixos/modules/services/system/automatic-timezoned.nix b/nixos/modules/services/system/automatic-timezoned.nix
new file mode 100644
index 000000000000..9bdd64dd33a3
--- /dev/null
+++ b/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/nixos/modules/services/system/cachix-agent/default.nix b/nixos/modules/services/system/cachix-agent/default.nix
index 496e0b90355b..aa3b2153422c 100644
--- a/nixos/modules/services/system/cachix-agent/default.nix
+++ b/nixos/modules/services/system/cachix-agent/default.nix
@@ -8,32 +8,44 @@ 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;
-      description = "Agent name, usually same as the hostname";
+      description = lib.mdDoc "Agent name, usually same as the hostname";
       default = config.networking.hostName;
       defaultText = "config.networking.hostName";
     };
 
+    verbose = mkOption {
+      type = types.bool;
+      description = lib.mdDoc "Enable verbose output";
+      default = false;
+    };
+
     profile = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Profile name, defaults to 'system' (NixOS).";
+      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;
       defaultText = literalExpression "pkgs.cachix";
-      description = "Cachix Client package to use.";
+      description = lib.mdDoc "Cachix Client package to use.";
     };
 
     credentialsFile = mkOption {
       type = types.path;
       default = "/etc/cachix-agent.token";
-      description = ''
+      description = lib.mdDoc ''
         Required file that needs to contain CACHIX_AGENT_TOKEN=...
       '';
     };
@@ -45,12 +57,22 @@ in {
       after = ["network-online.target"];
       path = [ config.nix.package ];
       wantedBy = [ "multi-user.target" ];
-      # don't restart while changing
-      reloadIfChanged = true;
+
+      # Cachix requires $USER to be set
+      environment.USER = "root";
+
+      # don't stop the service if the unit disappears
+      unitConfig.X-StopOnRemoval = false;
+
       serviceConfig = {
+        # we don't want to kill children processes as those are deployments
+        KillMode = "process";
         Restart = "on-failure";
         EnvironmentFile = cfg.credentialsFile;
-        ExecStart = "${cfg.package}/bin/cachix 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} ${if cfg.profile != null then cfg.profile else ""}
+        '';
       };
     };
   };
diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix
new file mode 100644
index 000000000000..ec73c0bcdcfe
--- /dev/null
+++ b/nixos/modules/services/system/cachix-watch-store.nix
@@ -0,0 +1,87 @@
+{ 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 XZ compression (between 0 and 9)";
+      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" ];
+      serviceConfig = {
+        # 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/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix
index 8c6a6e294ebb..d75070dea434 100644
--- a/nixos/modules/services/system/cloud-init.nix
+++ b/nixos/modules/services/system/cloud-init.nix
@@ -20,14 +20,14 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.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 +39,7 @@ in
       btrfs.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Allow the cloud-init service to operate `btrfs` filesystem.
         '';
       };
@@ -47,7 +47,7 @@ in
       ext4.enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Allow the cloud-init service to operate `ext4` filesystem.
         '';
       };
@@ -55,7 +55,7 @@ in
       network.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Allow the cloud-init service to configure network interfaces
           through systemd-networkd.
         '';
@@ -81,7 +81,8 @@ in
            - write-files
            - growpart
            - resizefs
-           - update_etc_hosts
+           - update_hostname
+           - resolv_conf
            - ca-certs
            - rsyslog
            - users-groups
@@ -109,7 +110,7 @@ in
            - final-message
            - power-state-change
           '';
-        description = "cloud-init configuration.";
+        description = lib.mdDoc "cloud-init configuration.";
       };
 
     };
diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix
index d4cacb85694b..c677088101f0 100644
--- a/nixos/modules/services/system/dbus.nix
+++ b/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,11 +14,11 @@ let
     serviceDirectories = cfg.packages;
   };
 
+  inherit (lib) mkOption mkIf mkMerge types;
+
 in
 
 {
-  ###### interface
-
   options = {
 
     services.dbus = {
@@ -29,111 +27,165 @@ in
         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 = [ ];
-        description = ''
+        description = lib.mdDoc ''
           Packages whose D-Bus configuration files should be included in
           the configuration of the D-Bus system-wide or session-wide
           message bus.  Specifically, files in the following directories
           will be included into their respective DBus configuration paths:
-          <filename><replaceable>pkg</replaceable>/etc/dbus-1/system.d</filename>
-          <filename><replaceable>pkg</replaceable>/share/dbus-1/system.d</filename>
-          <filename><replaceable>pkg</replaceable>/share/dbus-1/system-services</filename>
-          <filename><replaceable>pkg</replaceable>/etc/dbus-1/session.d</filename>
-          <filename><replaceable>pkg</replaceable>/share/dbus-1/session.d</filename>
-          <filename><replaceable>pkg</replaceable>/share/dbus-1/services</filename>
+          {file}`«pkg»/etc/dbus-1/system.d`
+          {file}`«pkg»/share/dbus-1/system.d`
+          {file}`«pkg»/share/dbus-1/system-services`
+          {file}`«pkg»/etc/dbus-1/session.d`
+          {file}`«pkg»/share/dbus-1/session.d`
+          {file}`«pkg»/share/dbus-1/services`
         '';
       };
 
       apparmor = mkOption {
         type = types.enum [ "enabled" "disabled" "required" ];
-        description = ''
+        description = lib.mdDoc ''
           AppArmor mode for dbus.
 
-          <literal>enabled</literal> enables mediation when it's
-          supported in the kernel, <literal>disabled</literal>
+          `enabled` enables mediation when it's
+          supported in the kernel, `disabled`
           always disables AppArmor even with kernel support, and
-          <literal>required</literal> fails when AppArmor was not found
+          `required` fails when AppArmor was not found
           in the kernel.
         '';
         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"
+      ];
 
-    environment.systemPackages = [ pkgs.dbus.daemon pkgs.dbus ];
+      users.users.messagebus = {
+        uid = config.ids.uids.messagebus;
+        description = "D-Bus system message bus daemon user";
+        home = homeDir;
+        group = "messagebus";
+      };
 
-    environment.etc."dbus-1".source = configDir;
+      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 (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";
+      };
 
-    users.users.messagebus = {
-      uid = config.ids.uids.messagebus;
-      description = "D-Bus system message bus daemon user";
-      home = homeDir;
-      group = "messagebus";
-    };
+      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.groups.messagebus.gid = config.ids.gids.messagebus;
+      systemd.user.services.dbus = {
+        # Don't restart dbus-daemon. Bad things tend to happen if we do.
+        reloadIfChanged = true;
+        restartTriggers = [
+          configDir
+        ];
+      };
 
-    systemd.packages = [ pkgs.dbus.daemon ];
+    })
 
-    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";
-    };
+    (mkIf (cfg.implementation == "broker") {
+      environment.systemPackages = [
+        pkgs.dbus-broker
+      ];
 
-    services.dbus.packages = [
-      pkgs.dbus.out
-      config.system.path
-    ];
+      systemd.packages = [
+        pkgs.dbus-broker
+      ];
 
-    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; };
-    };
+      # Just to be sure we don't restart through the unit alias
+      systemd.services.dbus.reloadIfChanged = true;
+      systemd.user.services.dbus.reloadIfChanged = true;
 
-    systemd.user = {
-      services.dbus = {
-        # Don't restart dbus-daemon. Bad things tend to happen if we do.
+      # 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 ];
+        restartTriggers = [
+          configDir
+        ];
+        environment = {
+          LD_LIBRARY_PATH = config.system.nssModules.path;
+        };
       };
-      sockets.dbus.wantedBy = [ "sockets.target" ];
-    };
 
-    environment.pathsToLink = [ "/etc/dbus-1" "/share/dbus-1" ];
-  };
+      systemd.user.services.dbus-broker = {
+        aliases = [
+          "dbus.service"
+        ];
+        # Don't restart dbus. Bad things tend to happen if we do.
+        reloadIfChanged = true;
+        restartTriggers = [
+          configDir
+        ];
+      };
+    })
+
+  ]);
 }
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index 629358559890..3f501d453460 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -11,60 +11,60 @@ 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;
       default = 10;
-      description = ''
+      description = lib.mdDoc ''
         Minimum available memory (in percent).
 
         If the available memory falls below this threshold (and the analog is true for
-        <option>freeSwapThreshold</option>) the killing begins.
+        {option}`freeSwapThreshold`) the killing begins.
         SIGTERM is sent first to the process that uses the most memory; then, if the available
-        memory falls below <option>freeMemKillThreshold</option> (and the analog is true for
-        <option>freeSwapKillThreshold</option>), SIGKILL is sent.
+        memory falls below {option}`freeMemKillThreshold` (and the analog is true for
+        {option}`freeSwapKillThreshold`), SIGKILL is sent.
 
-        See <link xlink:href="https://github.com/rfjakob/earlyoom#command-line-options">README</link> for details.
+        See [README](https://github.com/rfjakob/earlyoom#command-line-options) for details.
       '';
     };
 
     freeMemKillThreshold = mkOption {
       type = types.nullOr (types.ints.between 1 100);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Minimum available memory (in percent) before sending SIGKILL.
-        If unset, this defaults to half of <option>freeMemThreshold</option>.
+        If unset, this defaults to half of {option}`freeMemThreshold`.
 
-        See the description of <xref linkend="opt-services.earlyoom.freeMemThreshold"/>.
+        See the description of [](#opt-services.earlyoom.freeMemThreshold).
       '';
     };
 
     freeSwapThreshold = mkOption {
       type = types.ints.between 1 100;
       default = 10;
-      description = ''
+      description = lib.mdDoc ''
         Minimum free swap space (in percent) before sending SIGTERM.
 
-        See the description of <xref linkend="opt-services.earlyoom.freeMemThreshold"/>.
+        See the description of [](#opt-services.earlyoom.freeMemThreshold).
       '';
     };
 
     freeSwapKillThreshold = mkOption {
       type = types.nullOr (types.ints.between 1 100);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Minimum free swap space (in percent) before sending SIGKILL.
-        If unset, this defaults to half of <option>freeSwapThreshold</option>.
+        If unset, this defaults to half of {option}`freeSwapThreshold`.
 
-        See the description of <xref linkend="opt-services.earlyoom.freeMemThreshold"/>.
+        See the description of [](#opt-services.earlyoom.freeMemThreshold).
       '';
     };
 
     enableDebugInfo = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable debugging messages.
       '';
     };
@@ -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.
       '';
     };
 
@@ -95,11 +95,11 @@ in
           echo "Process $EARLYOOM_NAME ($EARLYOOM_PID) was killed" >> /path/to/log
         '''
       '';
-      description = ''
+      description = lib.mdDoc ''
         An absolute path to an executable to be run for each process killed.
         Some environment variables are available, see
-        <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> and
-        <link xlink:href="https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#-n-pathtoscript">the man page</link>
+        [README](https://github.com/rfjakob/earlyoom#notifications) and
+        [the man page](https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#-n-pathtoscript)
         for details.
       '';
     };
@@ -108,14 +108,14 @@ in
       type = types.int;
       default = 3600;
       example = 0;
-      description = "Interval (in seconds) at which a memory report is printed (set to 0 to disable).";
+      description = lib.mdDoc "Interval (in seconds) at which a memory report is printed (set to 0 to disable).";
     };
 
     extraArgs = mkOption {
       type = types.listOf types.str;
       default = [];
       example = [ "-g" "--prefer '(^|/)(java|chromium)$'" ];
-      description = "Extra command-line arguments to be passed to earlyoom.";
+      description = lib.mdDoc "Extra command-line arguments to be passed to earlyoom.";
     };
   };
 
diff --git a/nixos/modules/services/system/kerberos/default.nix b/nixos/modules/services/system/kerberos/default.nix
index 9a1e67399010..4ed48e463741 100644
--- a/nixos/modules/services/system/kerberos/default.nix
+++ b/nixos/modules/services/system/kerberos/default.nix
@@ -9,19 +9,19 @@ let
     options = {
       principal = mkOption {
         type = types.str;
-        description = "Which principal the rule applies to";
+        description = lib.mdDoc "Which principal the rule applies to";
       };
       access = mkOption {
         type = types.either
           (types.listOf (types.enum ["add" "cpw" "delete" "get" "list" "modify"]))
           (types.enum ["all"]);
         default = "all";
-        description = "The changes the principal is allowed to make.";
+        description = lib.mdDoc "The changes the principal is allowed to make.";
       };
       target = mkOption {
         type = types.str;
         default = "*";
-        description = "The principals that 'access' applies to.";
+        description = lib.mdDoc "The principals that 'access' applies to.";
       };
     };
   };
@@ -34,7 +34,7 @@ let
           { principal = "*/admin"; access = "all"; }
           { principal = "admin"; access = "all"; }
         ];
-        description = ''
+        description = lib.mdDoc ''
           The privileges granted to a user.
         '';
       };
@@ -51,11 +51,11 @@ 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);
-        description = ''
+        description = lib.mdDoc ''
           The realm(s) to serve keys for.
         '';
       };
diff --git a/nixos/modules/services/system/kerberos/mit.nix b/nixos/modules/services/system/kerberos/mit.nix
index 25d7d51e808a..112000140453 100644
--- a/nixos/modules/services/system/kerberos/mit.nix
+++ b/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/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix
deleted file mode 100644
index 8f23454af9df..000000000000
--- a/nixos/modules/services/system/localtime.nix
+++ /dev/null
@@ -1,49 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.localtime;
-in {
-  options = {
-    services.localtime = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable <literal>localtime</literal>, 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.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.geoclue2 = {
-      enable = true;
-      appConfig.localtime = {
-        isAllowed = true;
-        isSystem = true;
-      };
-    };
-
-    # Install the polkit rules.
-    environment.systemPackages = [ pkgs.localtime ];
-    # Install the systemd unit.
-    systemd.packages = [ pkgs.localtime ];
-
-    users.users.localtimed = {
-      description = "localtime daemon";
-      isSystemUser = true;
-      group = "localtimed";
-    };
-    users.groups.localtimed = {};
-
-    systemd.services.localtime = {
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig.Restart = "on-failure";
-    };
-  };
-}
diff --git a/nixos/modules/services/system/localtimed.nix b/nixos/modules/services/system/localtimed.nix
new file mode 100644
index 000000000000..345bdbd8dda0
--- /dev/null
+++ b/nixos/modules/services/system/localtimed.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.localtimed;
+in {
+  imports = [ (lib.mkRenamedOptionModule [ "services" "localtime" ] [ "services" "localtimed" ]) ];
+
+  options = {
+    services.localtimed = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable `localtimed`, a simple daemon for keeping the
+          system timezone up-to-date based on the current location. It uses
+          geoclue2 to determine the current location.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.geoclue2.appConfig.localtimed = {
+      isAllowed = true;
+      isSystem = true;
+      users = [ (toString config.ids.uids.localtimed) ];
+    };
+
+    # Install the polkit rules.
+    environment.systemPackages = [ pkgs.localtime ];
+
+    systemd.services.localtimed = {
+      wantedBy = [ "multi-user.target" ];
+      partOf = [ "localtimed-geoclue-agent.service" ];
+      after = [ "localtimed-geoclue-agent.service" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.localtime}/bin/localtimed";
+        Restart = "on-failure";
+        Type = "exec";
+        User = "localtimed";
+      };
+    };
+
+    systemd.services.localtimed-geoclue-agent = {
+      wantedBy = [ "multi-user.target" ];
+      partOf = [ "geoclue.service" ];
+      after = [ "geoclue.service" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.geoclue2-with-demo-agent}/libexec/geoclue-2.0/demos/agent";
+        Restart = "on-failure";
+        Type = "exec";
+        User = "localtimed";
+      };
+    };
+
+    users = {
+      users.localtimed = {
+        uid = config.ids.uids.localtimed;
+        group = "localtimed";
+      };
+      groups.localtimed.gid = config.ids.gids.localtimed;
+    };
+  };
+}
diff --git a/nixos/modules/services/system/nscd.nix b/nixos/modules/services/system/nscd.nix
index 0caebc8ce90a..fdc5190d084b 100644
--- a/nixos/modules/services/system/nscd.nix
+++ b/nixos/modules/services/system/nscd.nix
@@ -20,30 +20,59 @@ in
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Name Service Cache Daemon.
           Disabling this is strongly discouraged, as this effectively disables NSS Lookups
           from all non-glibc NSS modules, including the ones provided by systemd.
         '';
       };
 
+      enableNsncd = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to use nsncd instead of nscd.
+          This is a nscd-compatible daemon, that proxies lookups, without any caching.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nscd";
+        description = lib.mdDoc ''
+          User account under which nscd runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nscd";
+        description = lib.mdDoc ''
+          User group under which nscd runs.
+        '';
+      };
+
       config = mkOption {
         type = types.lines;
         default = builtins.readFile ./nscd.conf;
-        description = "Configuration to use for Name Service Cache Daemon.";
+        description = lib.mdDoc "Configuration to use for Name Service Cache Daemon.";
       };
 
       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 = literalExample ''
+        defaultText = lib.literalExpression ''
           if pkgs.stdenv.hostPlatform.libc == "glibc"
             then pkgs.stdenv.cc.libc.bin
             else pkgs.glibc.bin;
         '';
-        description = "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.
+        '';
       };
 
     };
@@ -56,41 +85,65 @@ in
   config = mkIf cfg.enable {
     environment.etc."nscd.conf".text = cfg.config;
 
+    users.users.${cfg.user} = {
+      isSystemUser = true;
+      group = 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" ];
         wantedBy = [ "multi-user.target" ];
+        requiredBy = [ "nss-lookup.target" "nss-user-lookup.target" ];
 
         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
-        ];
-
-        # We use DynamicUser because in default configurations nscd doesn't
-        # create any files that need to survive restarts. However, in some
-        # configurations, nscd needs to be started as root; it will drop
-        # privileges after all the NSS modules have read their configuration
-        # files. So prefix the ExecStart command with "!" to prevent systemd
-        # from dropping privileges early. See ExecStart in systemd.service(5).
+        ] ++ 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
+        # configuration files. So prefix the ExecStart command with "!" to
+        # prevent systemd from dropping privileges early. See ExecStart in
+        # systemd.service(5). We use a static user, because some NSS modules
+        # 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";
-            DynamicUser = true;
+          {
+            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;
+            PrivateTmp = true;
+            NoNewPrivileges = true;
+            RestrictSUIDSGID = true;
+            ProtectSystem = "strict";
+            ProtectHome = "read-only";
             RuntimeDirectory = "nscd";
             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/nixos/modules/services/system/saslauthd.nix b/nixos/modules/services/system/saslauthd.nix
index 466b0ca60a7e..09720146aaa9 100644
--- a/nixos/modules/services/system/saslauthd.nix
+++ b/nixos/modules/services/system/saslauthd.nix
@@ -16,25 +16,25 @@ 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;
         defaultText = literalExpression "pkgs.cyrus_sasl.bin";
         type = types.package;
-        description = "Cyrus SASL package to use.";
+        description = lib.mdDoc "Cyrus SASL package to use.";
       };
 
       mechanism = mkOption {
         type = types.str;
         default = "pam";
-        description = "Auth mechanism to use";
+        description = lib.mdDoc "Auth mechanism to use";
       };
 
       config = mkOption {
         type = types.lines;
         default = "";
-        description = "Configuration to use for Cyrus SASL authentication daemon.";
+        description = lib.mdDoc "Configuration to use for Cyrus SASL authentication daemon.";
       };
 
     };
diff --git a/nixos/modules/services/system/self-deploy.nix b/nixos/modules/services/system/self-deploy.nix
index d7130a13c731..9b1ebfd37522 100644
--- a/nixos/modules/services/system/self-deploy.nix
+++ b/nixos/modules/services/system/self-deploy.nix
@@ -23,14 +23,14 @@ let
 in
 {
   options.services.self-deploy = {
-    enable = lib.mkEnableOption "self-deploy";
+    enable = lib.mkEnableOption (lib.mdDoc "self-deploy");
 
     nixFile = lib.mkOption {
       type = lib.types.path;
 
       default = "/default.nix";
 
-      description = ''
+      description = lib.mdDoc ''
         Path to nix file in repository. Leading '/' refers to root of
         git repository.
       '';
@@ -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
@@ -88,7 +88,7 @@ in
 
       default = null;
 
-      description = ''
+      description = lib.mdDoc ''
         Path to SSH private key used to fetch private repositories over
         SSH.
       '';
@@ -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`.
 
@@ -139,6 +139,8 @@ in
 
       path = with pkgs; [
         git
+        gnutar
+        gzip
         nix
       ] ++ lib.optionals (cfg.switchCommand == "boot") [ systemd ];
 
diff --git a/nixos/modules/services/system/systembus-notify.nix b/nixos/modules/services/system/systembus-notify.nix
index e918bc552ece..269197b3997e 100644
--- a/nixos/modules/services/system/systembus-notify.nix
+++ b/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/nixos/modules/services/system/uptimed.nix b/nixos/modules/services/system/uptimed.nix
index 67a03876e19f..df08c0f26e98 100644
--- a/nixos/modules/services/system/uptimed.nix
+++ b/nixos/modules/services/system/uptimed.nix
@@ -12,8 +12,8 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Enable <literal>uptimed</literal>, allowing you to track
+        description = lib.mdDoc ''
+          Enable `uptimed`, allowing you to track
           your highest uptimes.
         '';
       };
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index cb0da9e83b42..de3d077daec9 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -37,12 +37,12 @@ in {
   options = {
     services = {
       deluge = {
-        enable = mkEnableOption "Deluge daemon";
+        enable = mkEnableOption (lib.mdDoc "Deluge daemon");
 
         openFilesLimit = mkOption {
           default = openFilesLimit;
           type = types.either types.int types.str;
-          description = ''
+          description = lib.mdDoc ''
             Number of files to allow deluged to open.
           '';
         };
@@ -60,25 +60,25 @@ in {
               listen_ports = [ ${toString listenPortsDefault} ];
             }
           '';
-          description = ''
+          description = lib.mdDoc ''
             Deluge core configuration for the core.conf file. Only has an effect
-            when <option>services.deluge.declarative</option> is set to
-            <literal>true</literal>. String values must be quoted, integer and
+            when {option}`services.deluge.declarative` is set to
+            `true`. String values must be quoted, integer and
             boolean values must not. See
-            <link xlink:href="https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41"/>
-            for the availaible options.
+            <https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41>
+            for the available options.
           '';
         };
 
         declarative = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to use a declarative deluge configuration.
-            Only if set to <literal>true</literal>, the options
-            <option>services.deluge.config</option>,
-            <option>services.deluge.openFirewall</option> and
-            <option>services.deluge.authFile</option> will be
+            Only if set to `true`, the options
+            {option}`services.deluge.config`,
+            {option}`services.deluge.openFirewall` and
+            {option}`services.deluge.authFile` will be
             applied.
           '';
         };
@@ -86,15 +86,15 @@ in {
         openFirewall = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Whether to open the firewall for the ports in
-            <option>services.deluge.config.listen_ports</option>. It only takes effet if
-            <option>services.deluge.declarative</option> is set to
-            <literal>true</literal>.
+            {option}`services.deluge.config.listen_ports`. It only takes effet if
+            {option}`services.deluge.declarative` is set to
+            `true`.
 
             It does NOT apply to the daemon port nor the web UI port. To access those
             ports secuerly check the documentation
-            <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel"/>
+            <https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel>
             or use a VPN or configure certificates for deluge.
           '';
         };
@@ -102,7 +102,7 @@ in {
         dataDir = mkOption {
           type = types.path;
           default = "/var/lib/deluge";
-          description = ''
+          description = lib.mdDoc ''
             The directory where deluge will create files.
           '';
         };
@@ -110,21 +110,21 @@ in {
         authFile = mkOption {
           type = types.path;
           example = "/run/keys/deluge-auth";
-          description = ''
+          description = lib.mdDoc ''
             The file managing the authentication for deluge, the format of this
             file is straightforward, each line contains a
             username:password:level tuple in plaintext. It only has an effect
-            when <option>services.deluge.declarative</option> is set to
-            <literal>true</literal>.
-            See <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/Authentication"/> for
-            more informations.
+            when {option}`services.deluge.declarative` is set to
+            `true`.
+            See <https://dev.deluge-torrent.org/wiki/UserGuide/Authentication> for
+            more information.
           '';
         };
 
         user = mkOption {
           type = types.str;
           default = "deluge";
-          description = ''
+          description = lib.mdDoc ''
             User account under which deluge runs.
           '';
         };
@@ -132,7 +132,7 @@ in {
         group = mkOption {
           type = types.str;
           default = "deluge";
-          description = ''
+          description = lib.mdDoc ''
             Group under which deluge runs.
           '';
         };
@@ -140,7 +140,7 @@ in {
         extraPackages = mkOption {
           type = types.listOf types.package;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             Extra packages available at runtime to enable Deluge's plugins. For example,
             extraction utilities are required for the built-in "Extractor" plugin.
             This always contains unzip, gnutar, xz and bzip2.
@@ -150,19 +150,19 @@ in {
         package = mkOption {
           type = types.package;
           example = literalExpression "pkgs.deluge-2_x";
-          description = ''
+          description = lib.mdDoc ''
             Deluge package to use.
           '';
         };
       };
 
       deluge.web = {
-        enable = mkEnableOption "Deluge Web daemon";
+        enable = mkEnableOption (lib.mdDoc "Deluge Web daemon");
 
         port = mkOption {
           type = types.port;
           default = 8112;
-          description = ''
+          description = lib.mdDoc ''
             Deluge web UI port.
           '';
         };
@@ -170,7 +170,7 @@ in {
         openFirewall = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Open ports in the firewall for deluge web daemon
           '';
         };
diff --git a/nixos/modules/services/torrent/flexget.nix b/nixos/modules/services/torrent/flexget.nix
index e500e02d861b..2a9ffac18d9b 100644
--- a/nixos/modules/services/torrent/flexget.nix
+++ b/nixos/modules/services/torrent/flexget.nix
@@ -14,40 +14,40 @@ let
 in {
   options = {
     services.flexget = {
-      enable = mkEnableOption "Run FlexGet Daemon";
+      enable = mkEnableOption (lib.mdDoc "Run FlexGet Daemon");
 
       user = mkOption {
         default = "deluge";
         example = "some_user";
         type = types.str;
-        description = "The user under which to run flexget.";
+        description = lib.mdDoc "The user under which to run flexget.";
       };
 
       homeDir = mkOption {
         default = "/var/lib/deluge";
         example = "/home/flexget";
         type = types.path;
-        description = "Where files live.";
+        description = lib.mdDoc "Where files live.";
       };
 
       interval = mkOption {
         default = "10m";
         example = "1h";
         type = types.str;
-        description = "When to perform a <command>flexget</command> run. See <command>man 7 systemd.time</command> for the format.";
+        description = lib.mdDoc "When to perform a {command}`flexget` run. See {command}`man 7 systemd.time` for the format.";
       };
 
       systemScheduler = mkOption {
         default = true;
         example = false;
         type = types.bool;
-        description = "When true, execute the runs via the flexget-runner.timer. If false, you have to specify the settings yourself in the YML file.";
+        description = lib.mdDoc "When true, execute the runs via the flexget-runner.timer. If false, you have to specify the settings yourself in the YML file.";
       };
 
       config = mkOption {
         default = "";
         type = types.lines;
-        description = "The YAML configuration for FlexGet.";
+        description = lib.mdDoc "The YAML configuration for FlexGet.";
       };
     };
   };
diff --git a/nixos/modules/services/torrent/magnetico.nix b/nixos/modules/services/torrent/magnetico.nix
index 3dd7b1ece768..b813f1205119 100644
--- a/nixos/modules/services/torrent/magnetico.nix
+++ b/nixos/modules/services/torrent/magnetico.nix
@@ -43,13 +43,13 @@ 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;
       default = "0.0.0.0";
       example = "1.2.3.4";
-      description = ''
+      description = lib.mdDoc ''
         Address to be used for indexing DHT nodes.
       '';
     };
@@ -57,17 +57,17 @@ in {
     crawler.port = mkOption {
       type = types.port;
       default = 0;
-      description = ''
+      description = lib.mdDoc ''
         Port to be used for indexing DHT nodes.
         This port should be added to
-        <option>networking.firewall.allowedTCPPorts</option>.
+        {option}`networking.firewall.allowedTCPPorts`.
       '';
     };
 
     crawler.maxNeighbors = mkOption {
       type = types.ints.positive;
       default = 1000;
-      description = ''
+      description = lib.mdDoc ''
         Maximum number of simultaneous neighbors of an indexer.
         Be careful changing this number: high values can very
         easily cause your network to be congested or even crash
@@ -78,7 +78,7 @@ in {
     crawler.maxLeeches = mkOption {
       type = types.ints.positive;
       default = 200;
-      description = ''
+      description = lib.mdDoc ''
         Maximum number of simultaneous leeches.
       '';
     };
@@ -86,7 +86,7 @@ in {
     crawler.extraOptions = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Extra command line arguments to pass to magneticod.
       '';
     };
@@ -95,7 +95,7 @@ in {
       type = types.str;
       default = "localhost";
       example = "1.2.3.4";
-      description = ''
+      description = lib.mdDoc ''
         Address the web interface will listen to.
       '';
     };
@@ -103,7 +103,7 @@ in {
     web.port = mkOption {
       type = types.port;
       default = 8080;
-      description = ''
+      description = lib.mdDoc ''
         Port the web interface will listen to.
       '';
     };
@@ -116,50 +116,48 @@ 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`
       '';
     };
 
     web.extraOptions = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Extra command line arguments to pass to magneticow.
       '';
     };
diff --git a/nixos/modules/services/torrent/opentracker.nix b/nixos/modules/services/torrent/opentracker.nix
index d76d61dfe859..7d67491c1191 100644
--- a/nixos/modules/services/torrent/opentracker.nix
+++ b/nixos/modules/services/torrent/opentracker.nix
@@ -5,11 +5,11 @@ let
   cfg = config.services.opentracker;
 in {
   options.services.opentracker = {
-    enable = mkEnableOption "opentracker";
+    enable = mkEnableOption (lib.mdDoc "opentracker");
 
     package = mkOption {
       type = types.package;
-      description = ''
+      description = lib.mdDoc ''
         opentracker package to use
       '';
       default = pkgs.opentracker;
@@ -18,7 +18,7 @@ in {
 
     extraOptions = mkOption {
       type = types.separatedString " ";
-      description = ''
+      description = lib.mdDoc ''
         Configuration Arguments for opentracker
         See https://erdgeist.org/arts/software/opentracker/ for all params
       '';
diff --git a/nixos/modules/services/torrent/peerflix.nix b/nixos/modules/services/torrent/peerflix.nix
index 821c829f6b4a..ea74d0f8b9c4 100644
--- a/nixos/modules/services/torrent/peerflix.nix
+++ b/nixos/modules/services/torrent/peerflix.nix
@@ -19,19 +19,19 @@ in {
 
   options.services.peerflix = {
     enable = mkOption {
-      description = "Whether to enable peerflix service.";
+      description = lib.mdDoc "Whether to enable peerflix service.";
       default = false;
       type = types.bool;
     };
 
     stateDir = mkOption {
-      description = "Peerflix state directory.";
+      description = lib.mdDoc "Peerflix state directory.";
       default = "/var/lib/peerflix";
       type = types.path;
     };
 
     downloadDir = mkOption {
-      description = "Peerflix temporary download directory.";
+      description = lib.mdDoc "Peerflix temporary download directory.";
       default = "${cfg.stateDir}/torrents";
       defaultText = literalExpression ''"''${config.${opt.stateDir}}/torrents"'';
       type = types.path;
diff --git a/nixos/modules/services/torrent/rtorrent.nix b/nixos/modules/services/torrent/rtorrent.nix
index 759dcfe2e6c5..627439e1079b 100644
--- a/nixos/modules/services/torrent/rtorrent.nix
+++ b/nixos/modules/services/torrent/rtorrent.nix
@@ -9,12 +9,12 @@ let
 
 in {
   options.services.rtorrent = {
-    enable = mkEnableOption "rtorrent";
+    enable = mkEnableOption (lib.mdDoc "rtorrent");
 
     dataDir = mkOption {
       type = types.str;
       default = "/var/lib/rtorrent";
-      description = ''
+      description = lib.mdDoc ''
         The directory where rtorrent stores its data files.
       '';
     };
@@ -23,7 +23,7 @@ in {
       type = types.str;
       default = "${cfg.dataDir}/download";
       defaultText = literalExpression ''"''${config.${opt.dataDir}}/download"'';
-      description = ''
+      description = lib.mdDoc ''
         Where to put downloaded files.
       '';
     };
@@ -31,7 +31,7 @@ in {
     user = mkOption {
       type = types.str;
       default = "rtorrent";
-      description = ''
+      description = lib.mdDoc ''
         User account under which rtorrent runs.
       '';
     };
@@ -39,7 +39,7 @@ in {
     group = mkOption {
       type = types.str;
       default = "rtorrent";
-      description = ''
+      description = lib.mdDoc ''
         Group under which rtorrent runs.
       '';
     };
@@ -48,7 +48,7 @@ in {
       type = types.package;
       default = pkgs.rtorrent;
       defaultText = literalExpression "pkgs.rtorrent";
-      description = ''
+      description = lib.mdDoc ''
         The rtorrent package to use.
       '';
     };
@@ -56,7 +56,7 @@ in {
     port = mkOption {
       type = types.port;
       default = 50000;
-      description = ''
+      description = lib.mdDoc ''
         The rtorrent port.
       '';
     };
@@ -64,8 +64,8 @@ in {
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to open the firewall for the port in <option>services.rtorrent.port</option>.
+      description = lib.mdDoc ''
+        Whether to open the firewall for the port in {option}`services.rtorrent.port`.
       '';
     };
 
@@ -73,7 +73,7 @@ in {
       type = types.str;
       readOnly = true;
       default = "/run/rtorrent/rpc.sock";
-      description = ''
+      description = lib.mdDoc ''
         RPC socket path.
       '';
     };
@@ -81,8 +81,8 @@ in {
     configText = mkOption {
       type = types.lines;
       default = "";
-      description = ''
-        The content of <filename>rtorrent.rc</filename>. The <link xlink:href="https://rtorrent-docs.readthedocs.io/en/latest/cookbook.html#modernized-configuration-template">modernized configuration template</link> with the values specified in this module will be prepended using mkBefore. You can use mkForce to overwrite the config completly.
+      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 completely.
       '';
     };
   };
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index d12d8aa23980..437823384833 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -24,23 +24,27 @@ in
   ];
   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 = ''
+        description = lib.mdDoc ''
           Settings whose options overwrite fields in
-          <literal>.config/transmission-daemon/settings.json</literal>
+          `.config/transmission-daemon/settings.json`
           (each time the service starts).
 
-          See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
-          for documentation of settings not explicitely covered by this module.
+          See [Transmission's Wiki](https://github.com/transmission/transmission/wiki/Editing-Configuration-Files)
+          for documentation of settings not explicitly covered by this module.
         '';
         default = {};
         type = types.submodule {
@@ -49,89 +53,89 @@ in
             type = types.path;
             default = "${cfg.home}/${downloadsDir}";
             defaultText = literalExpression ''"''${config.${opt.home}}/${downloadsDir}"'';
-            description = "Directory where to download torrents.";
+            description = lib.mdDoc "Directory where to download torrents.";
           };
           options.incomplete-dir = mkOption {
             type = types.path;
             default = "${cfg.home}/${incompleteDir}";
             defaultText = literalExpression ''"''${config.${opt.home}}/${incompleteDir}"'';
-            description = ''
+            description = lib.mdDoc ''
               When enabled with
               services.transmission.home
-              <xref linkend="opt-services.transmission.settings.incomplete-dir-enabled"/>,
+              [](#opt-services.transmission.settings.incomplete-dir-enabled),
               new torrents will download the files to this directory.
               When complete, the files will be moved to download-dir
-              <xref linkend="opt-services.transmission.settings.download-dir"/>.
+              [](#opt-services.transmission.settings.download-dir).
             '';
           };
           options.incomplete-dir-enabled = mkOption {
             type = types.bool;
             default = true;
-            description = "";
+            description = lib.mdDoc "";
           };
           options.message-level = mkOption {
             type = types.ints.between 0 3;
             default = 2;
-            description = "Set verbosity of transmission messages.";
+            description = lib.mdDoc "Set verbosity of transmission messages.";
           };
           options.peer-port = mkOption {
             type = types.port;
             default = 51413;
-            description = "The peer port to listen for incoming connections.";
+            description = lib.mdDoc "The peer port to listen for incoming connections.";
           };
           options.peer-port-random-high = mkOption {
             type = types.port;
             default = 65535;
-            description = ''
+            description = lib.mdDoc ''
               The maximum peer port to listen to for incoming connections
-              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
+              when [](#opt-services.transmission.settings.peer-port-random-on-start) is enabled.
             '';
           };
           options.peer-port-random-low = mkOption {
             type = types.port;
             default = 65535;
-            description = ''
+            description = lib.mdDoc ''
               The minimal peer port to listen to for incoming connections
-              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
+              when [](#opt-services.transmission.settings.peer-port-random-on-start) is enabled.
             '';
           };
           options.peer-port-random-on-start = mkOption {
             type = types.bool;
             default = false;
-            description = "Randomize the peer port.";
+            description = lib.mdDoc "Randomize the peer port.";
           };
           options.rpc-bind-address = mkOption {
             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 {
             type = types.port;
             default = 9091;
-            description = "The RPC port to listen to.";
+            description = lib.mdDoc "The RPC port to listen to.";
           };
           options.script-torrent-done-enabled = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether to run
-              <xref linkend="opt-services.transmission.settings.script-torrent-done-filename"/>
+              [](#opt-services.transmission.settings.script-torrent-done-filename)
               at torrent completion.
             '';
           };
           options.script-torrent-done-filename = mkOption {
             type = types.nullOr types.path;
             default = null;
-            description = "Executable to be run at torrent completion.";
+            description = lib.mdDoc "Executable to be run at torrent completion.";
           };
           options.umask = mkOption {
             type = types.int;
             default = 2;
-            description = ''
+            description = lib.mdDoc ''
               Sets transmission's file mode creation mask.
               See the umask(2) manpage for more information.
               Users who want their saved torrents to be world-writable
@@ -143,78 +147,80 @@ in
           options.utp-enabled = mkOption {
             type = types.bool;
             default = true;
-            description = ''
-              Whether to enable <link xlink:href="http://en.wikipedia.org/wiki/Micro_Transport_Protocol">Micro Transport Protocol (µTP)</link>.
+            description = lib.mdDoc ''
+              Whether to enable [Micro Transport Protocol (µTP)](http://en.wikipedia.org/wiki/Micro_Transport_Protocol).
             '';
           };
           options.watch-dir = mkOption {
             type = types.path;
             default = "${cfg.home}/${watchDir}";
             defaultText = literalExpression ''"''${config.${opt.home}}/${watchDir}"'';
-            description = "Watch a directory for torrent files and add them to transmission.";
+            description = lib.mdDoc "Watch a directory for torrent files and add them to transmission.";
           };
           options.watch-dir-enabled = mkOption {
             type = types.bool;
             default = false;
-            description = ''Whether to enable the
-              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
+            description = lib.mdDoc ''Whether to enable the
+              [](#opt-services.transmission.settings.watch-dir).
             '';
           };
           options.trash-original-torrent-files = mkOption {
             type = types.bool;
             default = false;
-            description = ''Whether to delete torrents added from the
-              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
+            description = lib.mdDoc ''Whether to delete torrents added from the
+              [](#opt-services.transmission.settings.watch-dir).
             '';
           };
         };
       };
 
+      package = mkPackageOption pkgs "transmission" {};
+
       downloadDirPermissions = mkOption {
         type = with types; nullOr str;
         default = null;
         example = "770";
-        description = ''
-          If not <code>null</code>, is used as the permissions
-          set by <literal>systemd.activationScripts.transmission-daemon</literal>
-          on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>,
-          <xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
-          and <xref linkend="opt-services.transmission.settings.watch-dir"/>.
+        description = lib.mdDoc ''
+          If not `null`, is used as the permissions
+          set by `systemd.activationScripts.transmission-daemon`
+          on the directories [](#opt-services.transmission.settings.download-dir),
+          [](#opt-services.transmission.settings.incomplete-dir).
+          and [](#opt-services.transmission.settings.watch-dir).
           Note that you may also want to change
-          <xref linkend="opt-services.transmission.settings.umask"/>.
+          [](#opt-services.transmission.settings.umask).
         '';
       };
 
       home = mkOption {
         type = types.path;
         default = "/var/lib/transmission";
-        description = ''
-          The directory where Transmission will create <literal>${settingsDir}</literal>.
-          as well as <literal>${downloadsDir}/</literal> unless
-          <xref linkend="opt-services.transmission.settings.download-dir"/> is changed,
-          and <literal>${incompleteDir}/</literal> unless
-          <xref linkend="opt-services.transmission.settings.incomplete-dir"/> is changed.
+        description = lib.mdDoc ''
+          The directory where Transmission will create `${settingsDir}`.
+          as well as `${downloadsDir}/` unless
+          [](#opt-services.transmission.settings.download-dir) is changed,
+          and `${incompleteDir}/` unless
+          [](#opt-services.transmission.settings.incomplete-dir) is changed.
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "transmission";
-        description = "User account under which Transmission runs.";
+        description = lib.mdDoc "User account under which Transmission runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "transmission";
-        description = "Group account under which Transmission runs.";
+        description = lib.mdDoc "Group account under which Transmission runs.";
       };
 
       credentialsFile = mkOption {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to a JSON file to be merged with the settings.
           Useful to merge a file which is better kept out of the Nix store
-          to set secret config parameters like <code>rpc-password</code>.
+          to set secret config parameters like `rpc-password`.
         '';
         default = "/dev/null";
         example = "/var/lib/secrets/transmission/settings.json";
@@ -224,23 +230,27 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "--log-debug" ];
-        description = ''
+        description = lib.mdDoc ''
           Extra flags passed to the transmission command in the service definition.
         '';
       };
 
-      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
-        <code>peer-limit-global"</code>.
-        And be aware that these settings are quite aggressive
-        and might not suite your regular desktop use.
-        For instance, SSH sessions may time out more easily'';
+          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/nixos/modules/services/tracing/tempo.nix b/nixos/modules/services/tracing/tempo.nix
new file mode 100644
index 000000000000..4a098c31effe
--- /dev/null
+++ b/nixos/modules/services/tracing/tempo.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.tempo;
+
+  settingsFormat = pkgs.formats.yaml {};
+in {
+  options.services.tempo = {
+    enable = mkEnableOption (lib.mdDoc "Grafana Tempo");
+
+    settings = mkOption {
+      type = settingsFormat.type;
+      default = {};
+      description = lib.mdDoc ''
+        Specify the configuration for Tempo in Nix.
+
+        See https://grafana.com/docs/tempo/latest/configuration/ for available options.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Specify a path to a configuration file that Tempo should use.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # for tempo-cli and friends
+    environment.systemPackages = [ pkgs.tempo ];
+
+    assertions = [{
+      assertion = (
+        (cfg.settings == {}) != (cfg.configFile == null)
+      );
+      message  = ''
+        Please specify a configuration for Tempo with either
+        'services.tempo.settings' or
+        'services.tempo.configFile'.
+      '';
+    }];
+
+    systemd.services.tempo = {
+      description = "Grafana Tempo Service Daemon";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = let
+        conf = if cfg.configFile == null
+               then settingsFormat.generate "config.yaml" cfg.settings
+               else cfg.configFile;
+      in
+      {
+        ExecStart = "${pkgs.tempo}/bin/tempo --config.file=${conf}";
+        DynamicUser = true;
+        Restart = "always";
+        ProtectSystem = "full";
+        DevicePolicy = "closed";
+        NoNewPrivileges = true;
+        WorkingDirectory = "/var/lib/tempo";
+        StateDirectory = "tempo";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/ttys/getty.nix b/nixos/modules/services/ttys/getty.nix
index 7021a2c80f85..22ae9c27e5bc 100644
--- a/nixos/modules/services/ttys/getty.nix
+++ b/nixos/modules/services/ttys/getty.nix
@@ -34,7 +34,7 @@ in
       autologinUser = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Username of the account that will be automatically logged in at the console.
           If unspecified, a login prompt is shown as usual.
         '';
@@ -44,7 +44,7 @@ in
         type = types.path;
         default = "${pkgs.shadow}/bin/login";
         defaultText = literalExpression ''"''${pkgs.shadow}/bin/login"'';
-        description = ''
+        description = lib.mdDoc ''
           Path to the login binary executed by agetty.
         '';
       };
@@ -52,15 +52,13 @@ in
       loginOptions = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Template for arguments to be passed to
-          <citerefentry><refentrytitle>login</refentrytitle>
-          <manvolnum>1</manvolnum></citerefentry>.
+          {manpage}`login(1)`.
 
-          See <citerefentry><refentrytitle>agetty</refentrytitle>
-          <manvolnum>1</manvolnum></citerefentry> for details,
+          See {manpage}`agetty(1)` for details,
           including security considerations.  If unspecified, agetty
-          will not be invoked with a <option>--login-options</option>
+          will not be invoked with a {option}`--login-options`
           option.
         '';
         example = "-h darkstar -- \\u";
@@ -69,7 +67,7 @@ in
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           Additional arguments passed to agetty.
         '';
         example = [ "--nohostname" ];
@@ -77,7 +75,7 @@ in
 
       greetingLine = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Welcome line printed by agetty.
           The default shows current NixOS version label, machine type and tty.
         '';
@@ -86,7 +84,7 @@ in
       helpLine = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Help line printed by agetty below the welcome line.
           Used by the installation CD to give some hints on
           how to proceed.
@@ -104,6 +102,7 @@ in
     # Note: this is set here rather than up there so that changing
     # nixos.label would not rebuild manual pages
     services.getty.greetingLine = mkDefault ''<<< Welcome to NixOS ${config.system.nixos.label} (\m) - \l >>>'';
+    services.getty.helpLine = mkIf (config.documentation.nixos.enable && config.documentation.doc.enable) "\nRun 'nixos-help' for the NixOS manual.";
 
     systemd.services."getty@" =
       { serviceConfig.ExecStart = [
@@ -147,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/nixos/modules/services/ttys/gpm.nix b/nixos/modules/services/ttys/gpm.nix
index 308a6d3643a6..378f6b17732f 100644
--- a/nixos/modules/services/ttys/gpm.nix
+++ b/nixos/modules/services/ttys/gpm.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable GPM, the General Purpose Mouse daemon,
           which enables mouse support in virtual consoles.
         '';
@@ -28,7 +28,7 @@ in
       protocol = mkOption {
         type = types.str;
         default = "ps/2";
-        description = "Mouse protocol to use.";
+        description = lib.mdDoc "Mouse protocol to use.";
       };
 
     };
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index e02ab3cb6b32..f5a8d8b104d2 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -11,7 +11,7 @@ in {
   options = {
     services.kmscon = {
       enable = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Use kmscon as the virtual console instead of gettys.
           kmscon is a kms/dri-based userspace virtual terminal implementation.
           It supports a richer feature set than the standard linux console VT,
@@ -23,33 +23,33 @@ in {
       };
 
       hwRender = mkOption {
-        description = "Whether to use 3D hardware acceleration to render the console.";
+        description = lib.mdDoc "Whether to use 3D hardware acceleration to render the console.";
         type = types.bool;
         default = false;
       };
 
       fonts = mkOption {
-        description = "Fonts used by kmscon, in order of priority.";
+        description = lib.mdDoc "Fonts used by kmscon, in order of priority.";
         default = null;
         example = lib.literalExpression ''[ { name = "Source Code Pro"; package = pkgs.source-code-pro; } ]'';
         type = with types;
           let fontType = submodule {
                 options = {
-                  name = mkOption { type = str; description = "Font name, as used by fontconfig."; };
-                  package = mkOption { type = package; description = "Package providing the font."; };
+                  name = mkOption { type = str; description = lib.mdDoc "Font name, as used by fontconfig."; };
+                  package = mkOption { type = package; description = lib.mdDoc "Package providing the font."; };
                 };
           }; in nullOr (nonEmptyListOf fontType);
       };
 
       extraConfig = mkOption {
-        description = "Extra contents of the kmscon.conf file.";
+        description = lib.mdDoc "Extra contents of the kmscon.conf file.";
         type = types.lines;
         default = "";
         example = "font-size=14";
       };
 
       extraOptions = mkOption {
-        description = "Extra flags to pass to kmscon.";
+        description = lib.mdDoc "Extra flags to pass to kmscon.";
         type = types.separatedString " ";
         default = "";
         example = "--term xterm-256color";
@@ -58,7 +58,7 @@ in {
       autologinUser = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Username of the account that will be automatically logged in at the console.
           If unspecified, a login prompt is shown as usual.
         '';
diff --git a/nixos/modules/services/video/epgstation/default.nix b/nixos/modules/services/video/epgstation/default.nix
index 191f6eb52e57..3d1d7a27c216 100644
--- a/nixos/modules/services/video/epgstation/default.nix
+++ b/nixos/modules/services/video/epgstation/default.nix
@@ -78,39 +78,37 @@ 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 = "epgstation package to use";
+      description = lib.mdDoc "epgstation package to use";
     };
 
     usePreconfiguredStreaming = lib.mkOption {
       type = lib.types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Use preconfigured default streaming options.
 
         Upstream defaults:
-        <link xlink:href="https://github.com/l3tnun/EPGStation/blob/master/config/config.yml.template"/>
+        <https://github.com/l3tnun/EPGStation/blob/master/config/config.yml.template>
       '';
     };
 
     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.
+        :::
       '';
     };
 
@@ -118,7 +116,7 @@ in
       name = lib.mkOption {
         type = lib.types.str;
         default = "epgstation";
-        description = ''
+        description = lib.mdDoc ''
           Name of the MySQL database that holds EPGStation's data.
         '';
       };
@@ -126,9 +124,9 @@ in
       passwordFile = lib.mkOption {
         type = lib.types.path;
         example = "/run/keys/epgstation-db-password";
-        description = ''
+        description = lib.mdDoc ''
           A file containing the password for the database named
-          <option>database.name</option>.
+          {option}`database.name`.
         '';
       };
     };
@@ -144,11 +142,11 @@ in
     # configure them according to their needs. In these cases, the value in the
     # upstream template configuration should serve as a "good enough" default.
     settings = lib.mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Options to add to config.yml.
 
         Documentation:
-        <link xlink:href="https://github.com/l3tnun/EPGStation/blob/master/doc/conf-manual.md"/>
+        <https://github.com/l3tnun/EPGStation/blob/master/doc/conf-manual.md>
       '';
 
       default = { };
@@ -163,7 +161,7 @@ in
         options.port = lib.mkOption {
           type = lib.types.port;
           default = 20772;
-          description = ''
+          description = lib.mdDoc ''
             HTTP port for EPGStation to listen on.
           '';
         };
@@ -172,9 +170,9 @@ in
           type = lib.types.port;
           default = cfg.settings.port + 1;
           defaultText = lib.literalExpression "config.${opt.settings}.port + 1";
-          description = ''
+          description = lib.mdDoc ''
             Socket.io port for EPGStation to listen on. It is valid to share
-            ports with <option>${opt.settings}.port</option>.
+            ports with {option}`${opt.settings}.port`.
           '';
         };
 
@@ -182,9 +180,9 @@ in
           type = lib.types.port;
           default = cfg.settings.socketioPort;
           defaultText = lib.literalExpression "config.${opt.settings}.socketioPort";
-          description = ''
+          description = lib.mdDoc ''
             Socket.io port that the web client is going to connect to. This may
-            be different from <option>${opt.settings}.socketioPort</option> if
+            be different from {option}`${opt.settings}.socketioPort` if
             EPGStation is hidden behind a reverse proxy.
           '';
         };
@@ -196,13 +194,13 @@ in
             "http+unix://''${lib.replaceStrings ["/"] ["%2F"] config.${option}}"
           '';
           example = "http://localhost:40772";
-          description = "URL to connect to Mirakurun.";
+          description = lib.mdDoc "URL to connect to Mirakurun.";
         };
 
         options.encodeProcessNum = lib.mkOption {
           type = lib.types.ints.positive;
           default = 4;
-          description = ''
+          description = lib.mdDoc ''
             The maximum number of processes that EPGStation would allow to run
             at the same time for encoding or streaming videos.
           '';
@@ -211,7 +209,7 @@ in
         options.concurrentEncodeNum = lib.mkOption {
           type = lib.types.ints.positive;
           default = 1;
-          description = ''
+          description = lib.mdDoc ''
             The maximum number of encoding jobs that EPGStation would run at the
             same time.
           '';
@@ -219,7 +217,7 @@ in
 
         options.encode = lib.mkOption {
           type = with lib.types; listOf attrs;
-          description = "Encoding presets for recorded videos.";
+          description = lib.mdDoc "Encoding presets for recorded videos.";
           default = [
             {
               name = "H.264";
diff --git a/nixos/modules/services/video/mirakurun.nix b/nixos/modules/services/video/mirakurun.nix
index 35303b2332c6..5484515e7cb4 100644
--- a/nixos/modules/services/video/mirakurun.nix
+++ b/nixos/modules/services/video/mirakurun.nix
@@ -24,13 +24,13 @@ 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;
           default = 40772;
-          description = ''
-            Port to listen on. If <literal>null</literal>, it won't listen on
+          description = lib.mdDoc ''
+            Port to listen on. If `null`, it won't listen on
             any port.
           '';
         };
@@ -38,24 +38,22 @@ 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.
+            :::
           '';
         };
 
         unixSocket = mkOption {
           type = with types; nullOr path;
           default = "/var/run/mirakurun/mirakurun.sock";
-          description = ''
-            Path to unix socket to listen on. If <literal>null</literal>, it
+          description = lib.mdDoc ''
+            Path to unix socket to listen on. If `null`, it
             won't listen on any unix sockets.
           '';
         };
@@ -63,7 +61,7 @@ in
         allowSmartCardAccess = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Install polkit rules to allow Mirakurun to access smart card readers
             which is commonly used along with tuner devices.
           '';
@@ -78,11 +76,11 @@ in
               overflowTimeLimit = 30000;
             };
           '';
-          description = ''
+          description = lib.mdDoc ''
             Options for server.yml.
 
             Documentation:
-            <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
+            <https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md>
           '';
         };
 
@@ -98,12 +96,12 @@ in
               }
             ];
           '';
-          description = ''
+          description = lib.mdDoc ''
             Options which are added to tuners.yml. If none is specified, it will
             automatically be generated at runtime.
 
             Documentation:
-            <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
+            <https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md>
           '';
         };
 
@@ -119,12 +117,12 @@ in
               }
             ];
           '';
-          description = ''
+          description = lib.mdDoc ''
             Options which are added to channels.yml. If none is specified, it
             will automatically be generated at runtime.
 
             Documentation:
-            <link xlink:href="https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md"/>
+            <https://github.com/Chinachu/Mirakurun/blob/master/doc/Configuration.md>
           '';
         };
       };
@@ -189,6 +187,7 @@ in
           CHANNELS_CONFIG_PATH = "/etc/mirakurun/channels.yml";
           SERVICES_DB_PATH = "/var/lib/mirakurun/services.json";
           PROGRAMS_DB_PATH = "/var/lib/mirakurun/programs.json";
+          LOGO_DATA_DIR_PATH = "/var/lib/mirakurun/logos";
           NODE_ENV = "production";
         };
 
diff --git a/nixos/modules/services/video/replay-sorcery.nix b/nixos/modules/services/video/replay-sorcery.nix
index abe7202a4a86..1be02f4d6da5 100644
--- a/nixos/modules/services/video/replay-sorcery.nix
+++ b/nixos/modules/services/video/replay-sorcery.nix
@@ -9,23 +9,23 @@ 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;
         default = false;
-        description = "Automatically start ReplaySorcery when graphical-session.target starts.";
+        description = lib.mdDoc "Automatically start ReplaySorcery when graphical-session.target starts.";
       };
 
       settings = mkOption {
         type = attrsOf (oneOf [ str int ]);
         default = {};
-        description = "System-wide configuration for ReplaySorcery (/etc/replay-sorcery.conf).";
+        description = lib.mdDoc "System-wide configuration for ReplaySorcery (/etc/replay-sorcery.conf).";
         example = literalExpression ''
           {
             videoInput = "hwaccel"; # requires `services.replay-sorcery.enableSysAdminCapability = true`
diff --git a/nixos/modules/services/video/rtsp-simple-server.nix b/nixos/modules/services/video/rtsp-simple-server.nix
index 644b1945a1ec..2dd62edab787 100644
--- a/nixos/modules/services/video/rtsp-simple-server.nix
+++ b/nixos/modules/services/video/rtsp-simple-server.nix
@@ -10,12 +10,12 @@ in
 {
   options = {
     services.rtsp-simple-server = {
-      enable = mkEnableOption "RTSP Simple Server";
+      enable = mkEnableOption (lib.mdDoc "RTSP Simple Server");
 
       settings = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Settings for rtsp-simple-server.
-          Read more at <link xlink:href="https://github.com/aler9/rtsp-simple-server/blob/main/rtsp-simple-server.yml"/>
+          Read more at <https://github.com/aler9/rtsp-simple-server/blob/main/rtsp-simple-server.yml>
         '';
         type = format.type;
 
@@ -40,7 +40,7 @@ in
 
       env = mkOption {
         type = with types; attrsOf anything;
-        description = "Extra environment variables for RTSP Simple Server";
+        description = lib.mdDoc "Extra environment variables for RTSP Simple Server";
         default = {};
         example = {
           RTSP_CONFKEY = "mykey";
diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix
index 11d9fe305470..93d4d5060b70 100644
--- a/nixos/modules/services/video/unifi-video.nix
+++ b/nixos/modules/services/video/unifi-video.nix
@@ -98,7 +98,7 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether or not to enable the unifi-video service.
       '';
     };
@@ -107,7 +107,7 @@ in
       type = types.package;
       default = pkgs.jre8;
       defaultText = literalExpression "pkgs.jre8";
-      description = ''
+      description = lib.mdDoc ''
         The JRE package to use. Check the release notes to ensure it is supported.
       '';
     };
@@ -116,7 +116,7 @@ in
       type = types.package;
       default = pkgs.unifi-video;
       defaultText = literalExpression "pkgs.unifi-video";
-      description = ''
+      description = lib.mdDoc ''
         The unifi-video package to use.
       '';
     };
@@ -125,7 +125,7 @@ in
       type = types.package;
       default = pkgs.mongodb-4_0;
       defaultText = literalExpression "pkgs.mongodb";
-      description = ''
+      description = lib.mdDoc ''
         The mongodb package to use.
       '';
     };
@@ -133,7 +133,7 @@ in
     logDir = mkOption {
       type = types.str;
       default = "${stateDir}/logs";
-      description = ''
+      description = lib.mdDoc ''
         Where to store the logs.
       '';
     };
@@ -141,15 +141,15 @@ in
     dataDir = mkOption {
       type = types.str;
       default = "${stateDir}/data";
-      description = ''
+      description = lib.mdDoc ''
         Where to store the database and other data.
       '';
     };
 
     openFirewall = mkOption {
       type = types.bool;
-      default = true;
-      description = ''
+      default = false;
+      description = lib.mdDoc ''
         Whether or not to open the required ports on the firewall.
       '';
     };
@@ -158,8 +158,8 @@ in
       type = types.nullOr types.int;
       default = 1024;
       example = 4096;
-      description = ''
-        Set the maximimum heap size for the JVM in MB.
+      description = lib.mdDoc ''
+        Set the maximum heap size for the JVM in MB.
       '';
     };
 
@@ -167,7 +167,7 @@ in
       type = types.path;
       default = "${cfg.dataDir}/unifi-video.pid";
       defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"'';
-      description = "Location of unifi-video pid file.";
+      description = lib.mdDoc "Location of unifi-video pid file.";
     };
 
   };
diff --git a/nixos/modules/services/wayland/cage.nix b/nixos/modules/services/wayland/cage.nix
index a32b81a916fc..330dce1d0c0f 100644
--- a/nixos/modules/services/wayland/cage.nix
+++ b/nixos/modules/services/wayland/cage.nix
@@ -5,12 +5,12 @@ 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;
     default = "demo";
-    description = ''
+    description = lib.mdDoc ''
       User to log-in as.
     '';
   };
@@ -19,7 +19,7 @@ in {
     type = types.listOf types.str;
     default = [];
     defaultText = literalExpression "[]";
-    description = "Additional command line arguments to pass to Cage.";
+    description = lib.mdDoc "Additional command line arguments to pass to Cage.";
     example = ["-d"];
   };
 
@@ -27,7 +27,7 @@ in {
     type = types.path;
     default = "${pkgs.xterm}/bin/xterm";
     defaultText = literalExpression ''"''${pkgs.xterm}/bin/xterm"'';
-    description = ''
+    description = lib.mdDoc ''
       Program to run in cage.
     '';
   };
@@ -88,7 +88,7 @@ in {
       account required pam_unix.so
       session required pam_unix.so
       session required pam_env.so conffile=/etc/pam/environment readenv=0
-      session required ${pkgs.systemd}/lib/security/pam_systemd.so
+      session required ${config.systemd.package}/lib/security/pam_systemd.so
     '';
 
     hardware.opengl.enable = mkDefault true;
diff --git a/nixos/modules/services/web-apps/alps.nix b/nixos/modules/services/web-apps/alps.nix
new file mode 100644
index 000000000000..1a58df2da1d2
--- /dev/null
+++ b/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}"
+        "smpts://${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/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index 2d809c17ff09..fe98c1777ea0 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -8,114 +8,122 @@ let
 
   pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
     enableSSO = cfg.sso.enable;
-    crowdProperties = ''
-      application.name                        ${cfg.sso.applicationName}
-      application.password                    ${cfg.sso.applicationPassword}
-      application.login.url                   ${cfg.sso.crowd}/console/
-
-      crowd.server.url                        ${cfg.sso.crowd}/services/
-      crowd.base.url                          ${cfg.sso.crowd}/
-
-      session.isauthenticated                 session.isauthenticated
-      session.tokenkey                        session.tokenkey
-      session.validationinterval              ${toString cfg.sso.validationInterval}
-      session.lastvalidation                  session.lastvalidation
-    '';
   });
 
+  crowdProperties = pkgs.writeText "crowd.properties" ''
+    application.name                        ${cfg.sso.applicationName}
+    application.password                    ${if cfg.sso.applicationPassword != null then cfg.sso.applicationPassword else "@NIXOS_CONFLUENCE_CROWD_SSO_PWD@"}
+    application.login.url                   ${cfg.sso.crowd}/console/
+
+    crowd.server.url                        ${cfg.sso.crowd}/services/
+    crowd.base.url                          ${cfg.sso.crowd}/
+
+    session.isauthenticated                 session.isauthenticated
+    session.tokenkey                        session.tokenkey
+    session.validationinterval              ${toString cfg.sso.validationInterval}
+    session.lastvalidation                  session.lastvalidation
+  '';
+
 in
 
 {
   options = {
     services.confluence = {
-      enable = mkEnableOption "Atlassian Confluence service";
+      enable = mkEnableOption (lib.mdDoc "Atlassian Confluence service");
 
       user = mkOption {
         type = types.str;
         default = "confluence";
-        description = "User which runs confluence.";
+        description = lib.mdDoc "User which runs confluence.";
       };
 
       group = mkOption {
         type = types.str;
         default = "confluence";
-        description = "Group which runs confluence.";
+        description = lib.mdDoc "Group which runs confluence.";
       };
 
       home = mkOption {
         type = types.str;
         default = "/var/lib/confluence";
-        description = "Home directory of the confluence instance.";
+        description = lib.mdDoc "Home directory of the confluence instance.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "Address to listen on.";
+        description = lib.mdDoc "Address to listen on.";
       };
 
       listenPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8090;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       catalinaOptions = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "-Xms1024m" "-Xmx2048m" "-Dconfluence.disable.peopledirectory.all=true" ];
-        description = "Java options to pass to catalina/tomcat.";
+        description = lib.mdDoc "Java options to pass to catalina/tomcat.";
       };
 
       proxy = {
-        enable = mkEnableOption "proxy support";
+        enable = mkEnableOption (lib.mdDoc "proxy support");
 
         name = mkOption {
           type = types.str;
           example = "confluence.example.com";
-          description = "Virtual hostname at the proxy";
+          description = lib.mdDoc "Virtual hostname at the proxy";
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 443;
           example = 80;
-          description = "Port used at the proxy";
+          description = lib.mdDoc "Port used at the proxy";
         };
 
         scheme = mkOption {
           type = types.str;
           default = "https";
           example = "http";
-          description = "Protocol used at the proxy.";
+          description = lib.mdDoc "Protocol used at the proxy.";
         };
       };
 
       sso = {
-        enable = mkEnableOption "SSO with Atlassian Crowd";
+        enable = mkEnableOption (lib.mdDoc "SSO with Atlassian Crowd");
 
         crowd = mkOption {
           type = types.str;
           example = "http://localhost:8095/crowd";
-          description = "Crowd Base URL without trailing slash";
+          description = lib.mdDoc "Crowd Base URL without trailing slash";
         };
 
         applicationName = mkOption {
           type = types.str;
           example = "jira";
-          description = "Exact name of this Confluence instance in Crowd";
+          description = lib.mdDoc "Exact name of this Confluence instance in Crowd";
         };
 
         applicationPassword = mkOption {
-          type = types.str;
-          description = "Application password of this Confluence instance in Crowd";
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc "Application password of this Confluence instance in Crowd";
+        };
+
+        applicationPasswordFile = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc "Path to the application password for Crowd of Confluence.";
         };
 
         validationInterval = mkOption {
           type = types.int;
           default = 2;
           example = 0;
-          description = ''
+          description = lib.mdDoc ''
             Set to 0, if you want authentication checks to occur on each
             request. Otherwise set to the number of minutes between request
             to validate if the user is logged in or out of the Crowd SSO
@@ -129,14 +137,14 @@ in
         type = types.package;
         default = pkgs.atlassian-confluence;
         defaultText = literalExpression "pkgs.atlassian-confluence";
-        description = "Atlassian Confluence package to use.";
+        description = lib.mdDoc "Atlassian Confluence package to use.";
       };
 
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
         defaultText = literalExpression "pkgs.oraclejre8";
-        description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+        description = lib.mdDoc "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
       };
     };
   };
@@ -147,6 +155,16 @@ in
       group = cfg.group;
     };
 
+    assertions = [
+      { assertion = cfg.sso.enable -> ((cfg.sso.applicationPassword == null) != (cfg.sso.applicationPasswordFile));
+        message = "Please set either applicationPassword or applicationPasswordFile";
+      }
+    ];
+
+    warnings = mkIf (cfg.sso.enable && cfg.sso.applicationPassword != null) [
+      "Using `services.confluence.sso.applicationPassword` is deprecated! Use `applicationPasswordFile` instead!"
+    ];
+
     users.groups.${cfg.group} = {};
 
     systemd.tmpfiles.rules = [
@@ -173,6 +191,7 @@ in
         CONF_USER = cfg.user;
         JAVA_HOME = "${cfg.jrePackage}";
         CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+        JAVA_OPTS = mkIf cfg.sso.enable "-Dcrowd.properties=${cfg.home}/crowd.properties";
       };
 
       preStart = ''
@@ -183,12 +202,24 @@ in
           -e 's,protocol="org.apache.coyote.http11.Http11NioProtocol",protocol="org.apache.coyote.http11.Http11NioProtocol" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}",' \
         '') + ''
           ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
+
+        ${optionalString cfg.sso.enable ''
+          install -m660 ${crowdProperties} ${cfg.home}/crowd.properties
+          ${optionalString (cfg.sso.applicationPasswordFile != null) ''
+            ${pkgs.replace-secret}/bin/replace-secret \
+              '@NIXOS_CONFLUENCE_CROWD_SSO_PWD@' \
+              ${cfg.sso.applicationPasswordFile} \
+              ${cfg.home}/crowd.properties
+          ''}
+        ''}
       '';
 
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
+        Restart = "on-failure";
+        RestartSec = "10";
         ExecStart = "${pkg}/bin/start-confluence.sh -fg";
         ExecStop = "${pkg}/bin/stop-confluence.sh";
       };
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index a8b2482d5a9c..c8d1eaef31d8 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -14,82 +14,104 @@ let
     proxyUrl = "${cfg.proxy.scheme}://${cfg.proxy.name}:${toString cfg.proxy.port}";
   });
 
+  crowdPropertiesFile = pkgs.writeText "crowd.properties" ''
+    application.name                        crowd-openid-server
+    application.password @NIXOS_CROWD_OPENID_PW@
+    application.base.url                    http://localhost:${toString cfg.listenPort}/openidserver
+    application.login.url                   http://localhost:${toString cfg.listenPort}/openidserver
+    application.login.url.template          http://localhost:${toString cfg.listenPort}/openidserver?returnToUrl=''${RETURN_TO_URL}
+
+    crowd.server.url                        http://localhost:${toString cfg.listenPort}/crowd/services/
+
+    session.isauthenticated                 session.isauthenticated
+    session.tokenkey                        session.tokenkey
+    session.validationinterval              0
+    session.lastvalidation                  session.lastvalidation
+  '';
+
 in
 
 {
   options = {
     services.crowd = {
-      enable = mkEnableOption "Atlassian Crowd service";
+      enable = mkEnableOption (lib.mdDoc "Atlassian Crowd service");
 
       user = mkOption {
         type = types.str;
         default = "crowd";
-        description = "User which runs Crowd.";
+        description = lib.mdDoc "User which runs Crowd.";
       };
 
       group = mkOption {
         type = types.str;
         default = "crowd";
-        description = "Group which runs Crowd.";
+        description = lib.mdDoc "Group which runs Crowd.";
       };
 
       home = mkOption {
         type = types.str;
         default = "/var/lib/crowd";
-        description = "Home directory of the Crowd instance.";
+        description = lib.mdDoc "Home directory of the Crowd instance.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "Address to listen on.";
+        description = lib.mdDoc "Address to listen on.";
       };
 
       listenPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8092;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       openidPassword = mkOption {
         type = types.str;
-        description = "Application password for OpenID server.";
+        default = "WILL_NEVER_BE_SET";
+        description = lib.mdDoc "Application password for OpenID server.";
+      };
+
+      openidPasswordFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = lib.mdDoc "Path to the file containing the application password for OpenID server.";
       };
 
       catalinaOptions = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "-Xms1024m" "-Xmx2048m" ];
-        description = "Java options to pass to catalina/tomcat.";
+        description = lib.mdDoc "Java options to pass to catalina/tomcat.";
       };
 
       proxy = {
-        enable = mkEnableOption "reverse proxy support";
+        enable = mkEnableOption (lib.mdDoc "reverse proxy support");
 
         name = mkOption {
           type = types.str;
           example = "crowd.example.com";
-          description = "Virtual hostname at the proxy";
+          description = lib.mdDoc "Virtual hostname at the proxy";
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 443;
           example = 80;
-          description = "Port used at the proxy";
+          description = lib.mdDoc "Port used at the proxy";
         };
 
         scheme = mkOption {
           type = types.str;
           default = "https";
           example = "http";
-          description = "Protocol used at the proxy.";
+          description = lib.mdDoc "Protocol used at the proxy.";
         };
 
         secure = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether the connections to the proxy should be considered secure.";
+          description = lib.mdDoc "Whether the connections to the proxy should be considered secure.";
         };
       };
 
@@ -97,14 +119,14 @@ in
         type = types.package;
         default = pkgs.atlassian-crowd;
         defaultText = literalExpression "pkgs.atlassian-crowd";
-        description = "Atlassian Crowd package to use.";
+        description = lib.mdDoc "Atlassian Crowd package to use.";
       };
 
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
         defaultText = literalExpression "pkgs.oraclejre8";
-        description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+        description = lib.mdDoc "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
       };
     };
   };
@@ -140,6 +162,7 @@ in
         JAVA_HOME = "${cfg.jrePackage}";
         CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
         CATALINA_TMPDIR = "/tmp";
+        JAVA_OPTS = mkIf (cfg.openidPasswordFile != null) "-Dcrowd.properties=${cfg.home}/crowd.properties";
       };
 
       preStart = ''
@@ -151,12 +174,22 @@ in
           -e 's,compression="on",compression="off" protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${boolToString cfg.proxy.secure}",' \
         '') + ''
           ${pkg}/apache-tomcat/conf/server.xml.dist > ${cfg.home}/server.xml
+
+        ${optionalString (cfg.openidPasswordFile != null) ''
+          install -m660 ${crowdPropertiesFile} ${cfg.home}/crowd.properties
+          ${pkgs.replace-secret}/bin/replace-secret \
+            '@NIXOS_CROWD_OPENID_PW@' \
+            ${cfg.openidPasswordFile} \
+            ${cfg.home}/crowd.properties
+        ''}
       '';
 
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
+        Restart = "on-failure";
+        RestartSec = "10";
         ExecStart = "${pkg}/start_crowd.sh -fg";
       };
     };
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
index a120f6cdb3d8..4cc858216944 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -8,120 +8,121 @@ let
 
   pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
     enableSSO = cfg.sso.enable;
-    crowdProperties = ''
-      application.name                        ${cfg.sso.applicationName}
-      application.password                    ${cfg.sso.applicationPassword}
-      application.login.url                   ${cfg.sso.crowd}/console/
-
-      crowd.server.url                        ${cfg.sso.crowd}/services/
-      crowd.base.url                          ${cfg.sso.crowd}/
-
-      session.isauthenticated                 session.isauthenticated
-      session.tokenkey                        session.tokenkey
-      session.validationinterval              ${toString cfg.sso.validationInterval}
-      session.lastvalidation                  session.lastvalidation
-    '';
   });
 
+  crowdProperties = pkgs.writeText "crowd.properties" ''
+    application.name                        ${cfg.sso.applicationName}
+    application.password                    @NIXOS_JIRA_CROWD_SSO_PWD@
+    application.login.url                   ${cfg.sso.crowd}/console/
+
+    crowd.server.url                        ${cfg.sso.crowd}/services/
+    crowd.base.url                          ${cfg.sso.crowd}/
+
+    session.isauthenticated                 session.isauthenticated
+    session.tokenkey                        session.tokenkey
+    session.validationinterval              ${toString cfg.sso.validationInterval}
+    session.lastvalidation                  session.lastvalidation
+  '';
+
 in
 
 {
   options = {
     services.jira = {
-      enable = mkEnableOption "Atlassian JIRA service";
+      enable = mkEnableOption (lib.mdDoc "Atlassian JIRA service");
 
       user = mkOption {
         type = types.str;
         default = "jira";
-        description = "User which runs JIRA.";
+        description = lib.mdDoc "User which runs JIRA.";
       };
 
       group = mkOption {
         type = types.str;
         default = "jira";
-        description = "Group which runs JIRA.";
+        description = lib.mdDoc "Group which runs JIRA.";
       };
 
       home = mkOption {
         type = types.str;
         default = "/var/lib/jira";
-        description = "Home directory of the JIRA instance.";
+        description = lib.mdDoc "Home directory of the JIRA instance.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "Address to listen on.";
+        description = lib.mdDoc "Address to listen on.";
       };
 
       listenPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 8091;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       catalinaOptions = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "-Xms1024m" "-Xmx2048m" ];
-        description = "Java options to pass to catalina/tomcat.";
+        description = lib.mdDoc "Java options to pass to catalina/tomcat.";
       };
 
       proxy = {
-        enable = mkEnableOption "reverse proxy support";
+        enable = mkEnableOption (lib.mdDoc "reverse proxy support");
 
         name = mkOption {
           type = types.str;
           example = "jira.example.com";
-          description = "Virtual hostname at the proxy";
+          description = lib.mdDoc "Virtual hostname at the proxy";
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 443;
           example = 80;
-          description = "Port used at the proxy";
+          description = lib.mdDoc "Port used at the proxy";
         };
 
         scheme = mkOption {
           type = types.str;
           default = "https";
           example = "http";
-          description = "Protocol used at the proxy.";
+          description = lib.mdDoc "Protocol used at the proxy.";
         };
 
         secure = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether the connections to the proxy should be considered secure.";
+          description = lib.mdDoc "Whether the connections to the proxy should be considered secure.";
         };
       };
 
       sso = {
-        enable = mkEnableOption "SSO with Atlassian Crowd";
+        enable = mkEnableOption (lib.mdDoc "SSO with Atlassian Crowd");
 
         crowd = mkOption {
           type = types.str;
           example = "http://localhost:8095/crowd";
-          description = "Crowd Base URL without trailing slash";
+          description = lib.mdDoc "Crowd Base URL without trailing slash";
         };
 
         applicationName = mkOption {
           type = types.str;
           example = "jira";
-          description = "Exact name of this JIRA instance in Crowd";
+          description = lib.mdDoc "Exact name of this JIRA instance in Crowd";
         };
 
-        applicationPassword = mkOption {
+        applicationPasswordFile = mkOption {
           type = types.str;
-          description = "Application password of this JIRA instance in Crowd";
+          description = lib.mdDoc "Path to the file containing the application password of this JIRA instance in Crowd";
         };
 
         validationInterval = mkOption {
           type = types.int;
           default = 2;
           example = 0;
-          description = ''
+          description = lib.mdDoc ''
             Set to 0, if you want authentication checks to occur on each
             request. Otherwise set to the number of minutes between request
             to validate if the user is logged in or out of the Crowd SSO
@@ -135,14 +136,14 @@ in
         type = types.package;
         default = pkgs.atlassian-jira;
         defaultText = literalExpression "pkgs.atlassian-jira";
-        description = "Atlassian JIRA package to use.";
+        description = lib.mdDoc "Atlassian JIRA package to use.";
       };
 
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
         defaultText = literalExpression "pkgs.oraclejre8";
-        description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+        description = lib.mdDoc "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
       };
     };
   };
@@ -181,6 +182,7 @@ in
         JIRA_HOME = cfg.home;
         JAVA_HOME = "${cfg.jrePackage}";
         CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+        JAVA_OPTS = mkIf cfg.sso.enable "-Dcrowd.properties=${cfg.home}/crowd.properties";
       };
 
       preStart = ''
@@ -191,15 +193,31 @@ in
           -e 's,protocol="HTTP/1.1",protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${toString cfg.proxy.secure}",' \
         '') + ''
           ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
+
+        ${optionalString cfg.sso.enable ''
+          install -m660 ${crowdProperties} ${cfg.home}/crowd.properties
+          ${pkgs.replace-secret}/bin/replace-secret \
+            '@NIXOS_JIRA_CROWD_SSO_PWD@' \
+            ${cfg.sso.applicationPasswordFile} \
+            ${cfg.home}/crowd.properties
+        ''}
       '';
 
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
+        Restart = "on-failure";
+        RestartSec = "10";
         ExecStart = "${pkg}/bin/start-jira.sh -fg";
         ExecStop = "${pkg}/bin/stop-jira.sh";
       };
     };
   };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "jira" "sso" "applicationPassword" ] ''
+      Use `applicationPasswordFile` instead!
+    '')
+  ];
 }
diff --git a/nixos/modules/services/web-apps/baget.nix b/nixos/modules/services/web-apps/baget.nix
index 3007dd4fbb26..e4d5a1faddb2 100644
--- a/nixos/modules/services/web-apps/baget.nix
+++ b/nixos/modules/services/web-apps/baget.nix
@@ -53,12 +53,12 @@ let
 in
 {
   options.services.baget = {
-    enable = mkEnableOption "BaGet NuGet-compatible server";
+    enable = mkEnableOption (lib.mdDoc "BaGet NuGet-compatible server");
 
     apiKeyFile = mkOption {
       type = types.path;
       example = "/root/baget.key";
-      description = ''
+      description = lib.mdDoc ''
         Private API key for BaGet.
       '';
     };
@@ -112,8 +112,8 @@ in
           };
         }
       '';
-      description = ''
-        Extra configuration options for BaGet. Refer to <link xlink:href="https://loic-sharma.github.io/BaGet/configuration/"/> for details.
+      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.
       '';
     };
diff --git a/nixos/modules/services/web-apps/bookstack.nix b/nixos/modules/services/web-apps/bookstack.nix
index 64a2767fab6e..d846c98577c8 100644
--- a/nixos/modules/services/web-apps/bookstack.nix
+++ b/nixos/modules/services/web-apps/bookstack.nix
@@ -34,25 +34,25 @@ in {
 
   options.services.bookstack = {
 
-    enable = mkEnableOption "BookStack";
+    enable = mkEnableOption (lib.mdDoc "BookStack");
 
     user = mkOption {
       default = "bookstack";
-      description = "User bookstack runs as.";
+      description = lib.mdDoc "User bookstack runs as.";
       type = types.str;
     };
 
     group = mkOption {
       default = "bookstack";
-      description = "Group bookstack runs as.";
+      description = lib.mdDoc "Group bookstack runs as.";
       type = types.str;
     };
 
     appKeyFile = mkOption {
-      description = ''
+      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>.
+        generated with `head -c 32 /dev/urandom | base64`.
       '';
       example = "/run/keys/bookstack-appkey";
       type = types.path;
@@ -60,21 +60,18 @@ 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 = ''
+      description = lib.mdDoc ''
         The hostname to serve BookStack on.
       '';
     };
 
     appURL = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The root URL that you want to host BookStack on. All URLs in BookStack 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 bookstack:update-url https://old.example.com https://new.example.com</code>
+        If you change this in the future you may need to run a command to update stored URLs in the database. Command example: `php artisan bookstack:update-url https://old.example.com https://new.example.com`
       '';
       default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
       defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
@@ -83,7 +80,7 @@ in {
     };
 
     dataDir = mkOption {
-      description = "BookStack data directory";
+      description = lib.mdDoc "BookStack data directory";
       default = "/var/lib/bookstack";
       type = types.path;
     };
@@ -92,37 +89,37 @@ in {
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Database host address.";
+        description = lib.mdDoc "Database host address.";
       };
       port = mkOption {
         type = types.port;
         default = 3306;
-        description = "Database host port.";
+        description = lib.mdDoc "Database host port.";
       };
       name = mkOption {
         type = types.str;
         default = "bookstack";
-        description = "Database name.";
+        description = lib.mdDoc "Database name.";
       };
       user = mkOption {
         type = types.str;
         default = user;
         defaultText = literalExpression "user";
-        description = "Database username.";
+        description = lib.mdDoc "Database username.";
       };
       passwordFile = mkOption {
         type = with types; nullOr path;
         default = null;
         example = "/run/keys/bookstack-dbpassword";
-        description = ''
+        description = lib.mdDoc ''
           A file containing the password corresponding to
-          <option>database.user</option>.
+          {option}`database.user`.
         '';
       };
       createLocally = mkOption {
         type = types.bool;
         default = false;
-        description = "Create the database and database user locally.";
+        description = lib.mdDoc "Create the database and database user locally.";
       };
     };
 
@@ -130,47 +127,47 @@ in {
       driver = mkOption {
         type = types.enum [ "smtp" "sendmail" ];
         default = "smtp";
-        description = "Mail driver to use.";
+        description = lib.mdDoc "Mail driver to use.";
       };
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Mail host address.";
+        description = lib.mdDoc "Mail host address.";
       };
       port = mkOption {
         type = types.port;
         default = 1025;
-        description = "Mail host port.";
+        description = lib.mdDoc "Mail host port.";
       };
       fromName = mkOption {
         type = types.str;
         default = "BookStack";
-        description = "Mail \"from\" name.";
+        description = lib.mdDoc "Mail \"from\" name.";
       };
       from = mkOption {
         type = types.str;
         default = "mail@bookstackapp.com";
-        description = "Mail \"from\" email.";
+        description = lib.mdDoc "Mail \"from\" email.";
       };
       user = mkOption {
         type = with types; nullOr str;
         default = null;
         example = "bookstack";
-        description = "Mail username.";
+        description = lib.mdDoc "Mail username.";
       };
       passwordFile = mkOption {
         type = with types; nullOr path;
         default = null;
         example = "/run/keys/bookstack-mailpassword";
-        description = ''
+        description = lib.mdDoc ''
           A file containing the password corresponding to
-          <option>mail.user</option>.
+          {option}`mail.user`.
         '';
       };
       encryption = mkOption {
         type = with types; nullOr (enum [ "tls" ]);
         default = null;
-        description = "SMTP encryption mechanism to use.";
+        description = lib.mdDoc "SMTP encryption mechanism to use.";
       };
     };
 
@@ -178,7 +175,7 @@ in {
       type = types.str;
       default = "18M";
       example = "1G";
-      description = "The maximum size for uploads (e.g. images).";
+      description = lib.mdDoc "The maximum size for uploads (e.g. images).";
     };
 
     poolConfig = mkOption {
@@ -191,8 +188,8 @@ in {
         "pm.max_spare_servers" = 4;
         "pm.max_requests" = 500;
       };
-      description = ''
-        Options for the bookstack PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+      description = lib.mdDoc ''
+        Options for the bookstack PHP pool. See the documentation on `php-fpm.conf`
         for details on configuration directives.
       '';
     };
@@ -213,7 +210,7 @@ in {
           enableACME = true;
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         With this option, you can customize the nginx virtualHost settings.
       '';
     };
@@ -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.
@@ -256,20 +253,20 @@ in {
           OIDC_ISSUER_DISCOVER = true;
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         BookStack configuration options to set in the
-        <filename>.env</filename> file.
+        {file}`.env` file.
 
-        Refer to <link xlink:href="https://www.bookstackapp.com/docs/"/>
+        Refer to <https://www.bookstackapp.com/docs/>
         for details on supported values.
 
         Settings containing secret data should be set to an attribute
-        set containing the attribute <literal>_secret</literal> - a
+        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 <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>
+        this: in the resulting {file}`.env` file, the
+        `OIDC_CLIENT_SECRET` key will be set to the
+        contents of the {file}`/run/keys/oidc_secret`
         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/nixos/modules/services/web-apps/calibre-web.nix b/nixos/modules/services/web-apps/calibre-web.nix
index 704cd2cfa8a7..143decfc0917 100644
--- a/nixos/modules/services/web-apps/calibre-web.nix
+++ b/nixos/modules/services/web-apps/calibre-web.nix
@@ -8,13 +8,13 @@ in
 {
   options = {
     services.calibre-web = {
-      enable = mkEnableOption "Calibre-Web";
+      enable = mkEnableOption (lib.mdDoc "Calibre-Web");
 
       listen = {
         ip = mkOption {
           type = types.str;
           default = "::1";
-          description = ''
+          description = lib.mdDoc ''
             IP address that Calibre-Web should listen on.
           '';
         };
@@ -22,7 +22,7 @@ in
         port = mkOption {
           type = types.port;
           default = 8083;
-          description = ''
+          description = lib.mdDoc ''
             Listen port for Calibre-Web.
           '';
         };
@@ -31,27 +31,27 @@ in
       dataDir = mkOption {
         type = types.str;
         default = "calibre-web";
-        description = ''
-          The directory below <filename>/var/lib</filename> where Calibre-Web stores its data.
+        description = lib.mdDoc ''
+          The directory below {file}`/var/lib` where Calibre-Web stores its data.
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "calibre-web";
-        description = "User account under which Calibre-Web runs.";
+        description = lib.mdDoc "User account under which Calibre-Web runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "calibre-web";
-        description = "Group account under which Calibre-Web runs.";
+        description = lib.mdDoc "Group account under which Calibre-Web runs.";
       };
 
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Open ports in the firewall for the server.
         '';
       };
@@ -60,7 +60,7 @@ in
         calibreLibrary = mkOption {
           type = types.nullOr types.path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Path to Calibre library.
           '';
         };
@@ -68,7 +68,7 @@ in
         enableBookConversion = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Configure path to the Calibre's ebook-convert in the DB.
           '';
         };
@@ -76,7 +76,7 @@ in
         enableBookUploading = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Allow books to be uploaded via Calibre-Web UI.
           '';
         };
@@ -85,7 +85,7 @@ in
           enable = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Enable authorization using auth proxy.
             '';
           };
@@ -93,7 +93,7 @@ in
           header = mkOption {
             type = types.str;
             default = "";
-            description = ''
+            description = lib.mdDoc ''
               Auth proxy header name.
             '';
           };
@@ -136,7 +136,7 @@ in
 
               ${pkgs.sqlite}/bin/sqlite3 ${appDb} "update settings set ${settings}"
             '' + optionalString (cfg.options.calibreLibrary != null) ''
-              test -f ${cfg.options.calibreLibrary}/metadata.db || { echo "Invalid Calibre library"; exit 1; }
+              test -f "${cfg.options.calibreLibrary}/metadata.db" || { echo "Invalid Calibre library"; exit 1; }
             ''
           );
 
diff --git a/nixos/modules/services/web-apps/changedetection-io.nix b/nixos/modules/services/web-apps/changedetection-io.nix
new file mode 100644
index 000000000000..fc00aee43516
--- /dev/null
+++ b/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.dnsname.enable = true;
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix
index 474e9140ae87..24e34e0c5833 100644
--- a/nixos/modules/services/web-apps/code-server.nix
+++ b/nixos/modules/services/web-apps/code-server.nix
@@ -11,18 +11,18 @@ in {
   ###### interface
   options = {
     services.code-server = {
-      enable = mkEnableOption "code-server";
+      enable = mkEnableOption (lib.mdDoc "code-server");
 
       package = mkOption {
         default = pkgs.code-server;
-        defaultText = "pkgs.code-server";
-        description = "Which code-server derivation to use.";
+        defaultText = lib.literalExpression "pkgs.code-server";
+        description = lib.mdDoc "Which code-server derivation to use.";
         type = types.package;
       };
 
       extraPackages = mkOption {
         default = [ ];
-        description = "Packages that are available in the PATH of code-server.";
+        description = lib.mdDoc "Packages that are available in the PATH of code-server.";
         example = "[ pkgs.go ]";
         type = types.listOf types.package;
       };
@@ -30,49 +30,49 @@ in {
       extraEnvironment = mkOption {
         type = types.attrsOf types.str;
         description =
-          "Additional environment variables to passed to code-server.";
+          lib.mdDoc "Additional environment variables to passed to code-server.";
         default = { };
         example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
       };
 
       extraArguments = mkOption {
         default = [ "--disable-telemetry" ];
-        description = "Additional arguments that passed to code-server";
+        description = lib.mdDoc "Additional arguments that passed to code-server";
         example = ''[ "--verbose" ]'';
         type = types.listOf types.str;
       };
 
       host = mkOption {
         default = "127.0.0.1";
-        description = "The host-ip to bind to.";
+        description = lib.mdDoc "The host-ip to bind to.";
         type = types.str;
       };
 
       port = mkOption {
         default = 4444;
-        description = "The port where code-server runs.";
+        description = lib.mdDoc "The port where code-server runs.";
         type = types.port;
       };
 
       auth = mkOption {
         default = "password";
-        description = "The type of authentication to use.";
+        description = lib.mdDoc "The type of authentication to use.";
         type = types.enum [ "none" "password" ];
       };
 
       hashedPassword = mkOption {
         default = "";
         description =
-          "Create the password with: 'echo -n 'thisismypassword' | npx argon2-cli -e'.";
+          lib.mdDoc "Create the password with: `echo -n 'thisismypassword' | npx argon2-cli -e`.";
         type = types.str;
       };
 
       user = mkOption {
         default = defaultUser;
         example = "yourUser";
-        description = ''
+        description = lib.mdDoc ''
           The user to run code-server as.
-          By default, a user named <literal>${defaultUser}</literal> will be created.
+          By default, a user named `${defaultUser}` will be created.
         '';
         type = types.str;
       };
@@ -80,9 +80,9 @@ in {
       group = mkOption {
         default = defaultGroup;
         example = "yourGroup";
-        description = ''
+        description = lib.mdDoc ''
           The group to run code-server under.
-          By default, a group named <literal>${defaultGroup}</literal> will be created.
+          By default, a group named `${defaultGroup}` will be created.
         '';
         type = types.str;
       };
@@ -90,7 +90,7 @@ in {
       extraGroups = mkOption {
         default = [ ];
         description =
-          "An array of additional groups for the <literal>${defaultUser}</literal> user.";
+          lib.mdDoc "An array of additional groups for the `${defaultUser}` user.";
         example = [ "docker" ];
         type = types.listOf types.str;
       };
@@ -109,7 +109,7 @@ 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 = "${cfg.package}/bin/code-server --bind-addr ${cfg.host}:${toString cfg.port} --auth ${cfg.auth} " + lib.escapeShellArgs cfg.extraArguments;
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         RuntimeDirectory = cfg.user;
         User = cfg.user;
diff --git a/nixos/modules/services/web-apps/convos.nix b/nixos/modules/services/web-apps/convos.nix
index 8be11eec9f31..cd9f9d885d69 100644
--- a/nixos/modules/services/web-apps/convos.nix
+++ b/nixos/modules/services/web-apps/convos.nix
@@ -7,26 +7,26 @@ let
 in
 {
   options.services.convos = {
-    enable = mkEnableOption "Convos";
+    enable = mkEnableOption (lib.mdDoc "Convos");
     listenPort = mkOption {
       type = types.port;
       default = 3000;
       example = 8080;
-      description = "Port the web interface should listen on";
+      description = lib.mdDoc "Port the web interface should listen on";
     };
     listenAddress = mkOption {
       type = types.str;
       default = "*";
       example = "127.0.0.1";
-      description = "Address or host the web interface should listen on";
+      description = lib.mdDoc "Address or host the web interface should listen on";
     };
     reverseProxy = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enables reverse proxy support. This will allow Convos to automatically
-        pick up the <literal>X-Forwarded-For</literal> and
-        <literal>X-Request-Base</literal> HTTP headers set in your reverse proxy
+        pick up the `X-Forwarded-For` and
+        `X-Request-Base` HTTP headers set in your reverse proxy
         web server. Note that enabling this option without a reverse proxy in
         front will be a security issue.
       '';
diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix
deleted file mode 100644
index e6772de768e0..000000000000
--- a/nixos/modules/services/web-apps/cryptpad.nix
+++ /dev/null
@@ -1,54 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.cryptpad;
-in
-{
-  options.services.cryptpad = {
-    enable = mkEnableOption "the Cryptpad service";
-
-    package = mkOption {
-      default = pkgs.cryptpad;
-      defaultText = literalExpression "pkgs.cryptpad";
-      type = types.package;
-      description = "
-        Cryptpad package to use.
-      ";
-    };
-
-    configFile = mkOption {
-      type = types.path;
-      default = "${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
-      defaultText = literalExpression ''"''${package}/lib/node_modules/cryptpad/config/config.example.js"'';
-      description = ''
-        Path to the JavaScript configuration file.
-
-        See <link
-        xlink:href="https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js"/>
-        for a configuration example.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.cryptpad = {
-      description = "Cryptpad Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "networking.target" ];
-      serviceConfig = {
-        DynamicUser = true;
-        Environment = [
-          "CRYPTPAD_CONFIG=${cfg.configFile}"
-          "HOME=%S/cryptpad"
-        ];
-        ExecStart = "${cfg.package}/bin/cryptpad";
-        PrivateTmp = true;
-        Restart = "always";
-        StateDirectory = "cryptpad";
-        WorkingDirectory = "%S/cryptpad";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix
index 4d4689a4cf24..1dcc6f7a7c5b 100644
--- a/nixos/modules/services/web-apps/dex.nix
+++ b/nixos/modules/services/web-apps/dex.nix
@@ -11,14 +11,25 @@ let
   settingsFormat = pkgs.formats.yaml {};
   configFile = settingsFormat.generate "config.yaml" filteredSettings;
 
-  startPreScript = pkgs.writeShellScript "dex-start-pre" (''
-  '' + (concatStringsSep "\n" (builtins.map (file: ''
-    ${pkgs.replace-secret}/bin/replace-secret '${file}' '${file}' /run/dex/config.yaml
-  '') secretFiles)));
+  startPreScript = pkgs.writeShellScript "dex-start-pre"
+    (concatStringsSep "\n" (map (file: ''
+      replace-secret '${file}' '${file}' /run/dex/config.yaml
+    '')
+    secretFiles));
 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 = 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.
+      '';
+    };
 
     settings = mkOption {
       type = settingsFormat.type;
@@ -45,9 +56,12 @@ in
           ];
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         The available options can be found in
-        <link xlink:href="https://github.com/dexidp/dex/blob/v${pkgs.dex.version}/config.yaml.dist">the example configuration</link>.
+        [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`.
       '';
     };
   };
@@ -57,15 +71,15 @@ in
       description = "dex identity provider";
       wantedBy = [ "multi-user.target" ];
       after = [ "networking.target" ] ++ (optional (cfg.settings.storage.type == "postgres") "postgresql.service");
-
+      path = with pkgs; [ replace-secret ];
       serviceConfig = {
         ExecStart = "${pkgs.dex-oidc}/bin/dex serve /run/dex/config.yaml";
         ExecStartPre = [
           "${pkgs.coreutils}/bin/install -m 600 ${configFile} /run/dex/config.yaml"
           "+${startPreScript}"
         ];
-        RuntimeDirectory = "dex";
 
+        RuntimeDirectory = "dex";
         AmbientCapabilities = "CAP_NET_BIND_SERVICE";
         BindReadOnlyPaths = [
           "/nix/store"
@@ -105,10 +119,12 @@ 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";
+      } // optionalAttrs (cfg.environmentFile != null) {
+        EnvironmentFile = cfg.environmentFile;
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index 7dbbf4a12fe5..1ab0e679a54b 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.discourse;
   opt = options.services.discourse;
 
-  # Keep in sync with https://github.com/discourse/discourse_docker/blob/master/image/base/Dockerfile#L5
+  # Keep in sync with https://github.com/discourse/discourse_docker/blob/main/image/base/slim.Dockerfile#L5
   upstreamPostgresqlVersion = lib.getVersion pkgs.postgresql_13;
 
   postgresqlPackage = if config.services.postgresql.enable then
@@ -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;
@@ -35,20 +35,17 @@ in
           plugins = lib.unique (p.enabledPlugins ++ cfg.plugins);
         };
         defaultText = lib.literalExpression "pkgs.discourse";
-        description = ''
+        description = lib.mdDoc ''
           The discourse package to use.
         '';
       };
 
       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 = ''
+        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.
@@ -81,7 +78,7 @@ in
         type = with lib.types; nullOr path;
         default = null;
         example = "/run/keys/ssl.cert";
-        description = ''
+        description = lib.mdDoc ''
           The path to the server SSL certificate. Set this to enable
           SSL.
         '';
@@ -91,7 +88,7 @@ in
         type = with lib.types; nullOr path;
         default = null;
         example = "/run/keys/ssl.key";
-        description = ''
+        description = lib.mdDoc ''
           The path to the server SSL certificate key. Set this to
           enable SSL.
         '';
@@ -100,11 +97,11 @@ 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 = ''
+        description = lib.mdDoc ''
           Whether an ACME certificate should be used to secure
           connections to the server.
         '';
@@ -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”.
         '';
       };
 
@@ -151,26 +147,26 @@ in
             };
           };
         '';
-        description = ''
+        description = lib.mdDoc ''
           Discourse site settings. These are the settings that can be
           changed from the UI. This only defines their default values:
           they can still be overridden from the UI.
 
           Available settings can be found by looking in the
-          <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">site_settings.yml</link>
+          [site_settings.yml](https://github.com/discourse/discourse/blob/master/config/site_settings.yml)
           file of the upstream distribution. To find a setting's path,
           you only need to care about the first two levels; i.e. its
           category and name. See the example.
 
           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>config/nixos_site_settings.json</filename> file,
-          the <literal>login.github_client_secret</literal> key will
+          {file}`config/nixos_site_settings.json` file,
+          the `login.github_client_secret` key will
           be set to the contents of the
-          <filename>/run/keys/discourse_github_client_secret</filename>
+          {file}`/run/keys/discourse_github_client_secret`
           file.
         '';
       };
@@ -179,7 +175,7 @@ in
         skipCreate = lib.mkOption {
           type = lib.types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Do not create the admin account, instead rely on other
             existing admin accounts.
           '';
@@ -188,7 +184,7 @@ in
         email = lib.mkOption {
           type = lib.types.str;
           example = "admin@example.com";
-          description = ''
+          description = lib.mdDoc ''
             The admin user email address.
           '';
         };
@@ -196,21 +192,21 @@ in
         username = lib.mkOption {
           type = lib.types.str;
           example = "admin";
-          description = ''
+          description = lib.mdDoc ''
             The admin user username.
           '';
         };
 
         fullName = lib.mkOption {
           type = lib.types.str;
-          description = ''
+          description = lib.mdDoc ''
             The admin user's full name.
           '';
         };
 
         passwordFile = lib.mkOption {
           type = lib.types.path;
-          description = ''
+          description = lib.mdDoc ''
             A path to a file containing the admin user's password.
 
             This should be a string, not a nix path, since nix paths are
@@ -222,8 +218,8 @@ in
       nginx.enable = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = ''
-          Whether an <literal>nginx</literal> virtual host should be
+        description = lib.mdDoc ''
+          Whether an `nginx` virtual host should be
           set up to serve Discourse. Only disable if you're planning
           to use a different web server, which is not recommended.
         '';
@@ -233,7 +229,7 @@ in
         pool = lib.mkOption {
           type = lib.types.int;
           default = 8;
-          description = ''
+          description = lib.mdDoc ''
             Database connection pool size.
           '';
         };
@@ -241,16 +237,16 @@ 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”.
           '';
         };
 
         passwordFile = lib.mkOption {
           type = with lib.types; nullOr path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             File containing the Discourse database user password.
 
             This should be a string, not a nix path, since nix paths are
@@ -261,18 +257,18 @@ in
         createLocally = lib.mkOption {
           type = lib.types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether a database should be automatically created on the
-            local host. Set this to <literal>false</literal> if you plan
+            local host. Set this to `false` if you plan
             on provisioning a local database yourself. This has no effect
-            if <option>services.discourse.database.host</option> is customized.
+            if {option}`services.discourse.database.host` is customized.
           '';
         };
 
         name = lib.mkOption {
           type = lib.types.str;
           default = "discourse";
-          description = ''
+          description = lib.mdDoc ''
             Discourse database name.
           '';
         };
@@ -280,7 +276,7 @@ in
         username = lib.mkOption {
           type = lib.types.str;
           default = "discourse";
-          description = ''
+          description = lib.mdDoc ''
             Discourse database user.
           '';
         };
@@ -288,10 +284,10 @@ in
         ignorePostgresqlVersion = lib.mkOption {
           type = lib.types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether to allow other versions of PostgreSQL than the
             recommended one. Only effective when
-            <option>services.discourse.database.createLocally</option>
+            {option}`services.discourse.database.createLocally`
             is enabled.
           '';
         };
@@ -301,7 +297,7 @@ in
         host = lib.mkOption {
           type = lib.types.str;
           default = "localhost";
-          description = ''
+          description = lib.mdDoc ''
             Redis server hostname.
           '';
         };
@@ -309,7 +305,7 @@ in
         passwordFile = lib.mkOption {
           type = with lib.types; nullOr path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             File containing the Redis password.
 
             This should be a string, not a nix path, since nix paths are
@@ -320,7 +316,7 @@ in
         dbNumber = lib.mkOption {
           type = lib.types.int;
           default = 0;
-          description = ''
+          description = lib.mdDoc ''
             Redis database number.
           '';
         };
@@ -329,7 +325,7 @@ in
           type = lib.types.bool;
           default = cfg.redis.host != "localhost";
           defaultText = lib.literalExpression ''config.${opt.redis.host} != "localhost"'';
-          description = ''
+          description = lib.mdDoc ''
             Connect to Redis with SSL.
           '';
         };
@@ -342,8 +338,8 @@ in
           defaultText = lib.literalExpression ''
             "''${if config.services.discourse.mail.incoming.enable then "notifications" else "noreply"}@''${config.services.discourse.hostname}"
           '';
-          description = ''
-            The <literal>from:</literal> email address used when
+          description = lib.mdDoc ''
+            The `from:` email address used when
             sending all essential system emails. The domain specified
             here must have SPF, DKIM and reverse PTR records set
             correctly for email to arrive.
@@ -353,10 +349,10 @@ in
         contactEmailAddress = lib.mkOption {
           type = lib.types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Email address of key contact responsible for this
             site. Used for critical notifications, as well as on the
-            <literal>/about</literal> contact form for urgent matters.
+            `/about` contact form for urgent matters.
           '';
         };
 
@@ -364,7 +360,7 @@ in
           serverAddress = lib.mkOption {
             type = lib.types.str;
             default = "localhost";
-            description = ''
+            description = lib.mdDoc ''
               The address of the SMTP server Discourse should use to
               send email.
             '';
@@ -373,7 +369,7 @@ in
           port = lib.mkOption {
             type = lib.types.port;
             default = 25;
-            description = ''
+            description = lib.mdDoc ''
               The port of the SMTP server Discourse should use to
               send email.
             '';
@@ -382,7 +378,7 @@ in
           username = lib.mkOption {
             type = with lib.types; nullOr str;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               The username of the SMTP server.
             '';
           };
@@ -390,7 +386,7 @@ in
           passwordFile = lib.mkOption {
             type = lib.types.nullOr lib.types.path;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               A file containing the password of the SMTP server account.
 
               This should be a string, not a nix path, since nix paths
@@ -402,7 +398,7 @@ in
             type = lib.types.str;
             default = cfg.hostname;
             defaultText = lib.literalExpression "config.${opt.hostname}";
-            description = ''
+            description = lib.mdDoc ''
               HELO domain to use for outgoing mail.
             '';
           };
@@ -410,7 +406,7 @@ in
           authentication = lib.mkOption {
             type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]);
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
@@ -418,7 +414,7 @@ in
           enableStartTLSAuto = lib.mkOption {
             type = lib.types.bool;
             default = true;
-            description = ''
+            description = lib.mdDoc ''
               Whether to try to use StartTLS.
             '';
           };
@@ -426,7 +422,7 @@ in
           opensslVerifyMode = lib.mkOption {
             type = lib.types.str;
             default = "peer";
-            description = ''
+            description = lib.mdDoc ''
               How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
@@ -434,7 +430,7 @@ in
           forceTLS = lib.mkOption {
             type = lib.types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Force implicit TLS as per RFC 8314 3.3.
             '';
           };
@@ -444,7 +440,7 @@ in
           enable = lib.mkOption {
             type = lib.types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Whether to set up Postfix to receive incoming mail.
             '';
           };
@@ -453,7 +449,7 @@ in
             type = lib.types.str;
             default = "%{reply_key}@${cfg.hostname}";
             defaultText = lib.literalExpression ''"%{reply_key}@''${config.services.discourse.hostname}"'';
-            description = ''
+            description = lib.mdDoc ''
               Template for reply by email incoming email address, for
               example: %{reply_key}@reply.example.com or
               replies+%{reply_key}@example.com
@@ -464,7 +460,7 @@ in
             type = lib.types.package;
             default = pkgs.discourse-mail-receiver;
             defaultText = lib.literalExpression "pkgs.discourse-mail-receiver";
-            description = ''
+            description = lib.mdDoc ''
               The discourse-mail-receiver package to use.
             '';
           };
@@ -472,10 +468,10 @@ in
           apiKeyFile = lib.mkOption {
             type = lib.types.nullOr lib.types.path;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               A file containing the Discourse API key used to add
               posts and messages from mail. If left at its default
-              value <literal>null</literal>, one will be automatically
+              value `null`, one will be automatically
               generated.
 
               This should be a string, not a nix path, since nix paths
@@ -494,17 +490,15 @@ 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.
         '';
       };
 
       sidekiqProcesses = lib.mkOption {
         type = lib.types.int;
         default = 1;
-        description = ''
+        description = lib.mdDoc ''
           How many Sidekiq processes should be spawned.
         '';
       };
@@ -512,7 +506,7 @@ in
       unicornTimeout = lib.mkOption {
         type = lib.types.int;
         default = 30;
-        description = ''
+        description = lib.mdDoc ''
           Time in seconds before a request to Unicorn times out.
 
           This can be raised if the system Discourse is running on is
@@ -604,7 +598,6 @@ in
       cors_origin = "";
       serve_static_assets = false;
       sidekiq_workers = 5;
-      rtl_css = false;
       connection_reaper_age = 30;
       connection_reaper_interval = 30;
       relative_url_root = null;
@@ -802,13 +795,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";
@@ -940,7 +933,6 @@ in
                   proxy_cache discourse;
                   proxy_cache_key "$scheme,$host,$request_uri";
                   proxy_cache_valid 200 301 302 7d;
-                  proxy_cache_valid any 1m;
                 '';
               };
               "/message-bus/" = proxy {
diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix
index 7f2ed82ee33e..f70da0829f44 100644
--- a/nixos/modules/services/web-apps/documize.nix
+++ b/nixos/modules/services/web-apps/documize.nix
@@ -12,13 +12,13 @@ let
 
 in {
   options.services.documize = {
-    enable = mkEnableOption "Documize Wiki";
+    enable = mkEnableOption (lib.mdDoc "Documize Wiki");
 
     stateDirectoryName = mkOption {
       type = types.str;
       default = "documize";
-      description = ''
-        The name of the directory below <filename>/var/lib/private</filename>
+      description = lib.mdDoc ''
+        The name of the directory below {file}`/var/lib/private`
         where documize runs in and stores, for example, backups.
       '';
     };
@@ -27,7 +27,7 @@ in {
       type = types.package;
       default = pkgs.documize-community;
       defaultText = literalExpression "pkgs.documize-community";
-      description = ''
+      description = lib.mdDoc ''
         Which package to use for documize.
       '';
     };
@@ -36,7 +36,7 @@ in {
       type = types.nullOr types.str;
       default = null;
       example = "3edIYV6c8B28b19fh";
-      description = ''
+      description = lib.mdDoc ''
         The salt string used to encode JWT tokens, if not set a random value will be generated.
       '';
     };
@@ -44,23 +44,23 @@ in {
     cert = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
-        The <filename>cert.pem</filename> file used for https.
+      description = lib.mdDoc ''
+        The {file}`cert.pem` file used for https.
       '';
     };
 
     key = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
-        The <filename>key.pem</filename> file used for https.
+      description = lib.mdDoc ''
+        The {file}`key.pem` file used for https.
       '';
     };
 
     port = mkOption {
       type = types.port;
       default = 5001;
-      description = ''
+      description = lib.mdDoc ''
         The http/https port number.
       '';
     };
@@ -68,7 +68,7 @@ in {
     forcesslport = mkOption {
       type = types.nullOr types.port;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Redirect given http port number to TLS.
       '';
     };
@@ -76,8 +76,8 @@ in {
     offline = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Set <literal>true</literal> for offline mode.
+      description = lib.mdDoc ''
+        Set `true` for offline mode.
       '';
       apply = v: if true == v then 1 else 0;
     };
@@ -85,44 +85,31 @@ 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`
       '';
     };
 
     location = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         reserved
       '';
     };
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index 1f8ca742db95..f0b3c7b2bcf8 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -7,7 +7,6 @@ let
   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" ''
     # acl.auth.php
@@ -60,27 +59,27 @@ let
   siteOpts = { config, lib, name, ... }:
     {
       options = {
-        enable = mkEnableOption "DokuWiki web application.";
+        enable = mkEnableOption (lib.mdDoc "DokuWiki web application.");
 
         package = mkOption {
           type = types.package;
           default = pkgs.dokuwiki;
           defaultText = literalExpression "pkgs.dokuwiki";
-          description = "Which DokuWiki package to use.";
+          description = lib.mdDoc "Which DokuWiki package to use.";
         };
 
         stateDir = mkOption {
           type = types.path;
           default = "/var/lib/dokuwiki/${name}/data";
-          description = "Location of the DokuWiki state directory.";
+          description = lib.mdDoc "Location of the DokuWiki state directory.";
         };
 
         acl = mkOption {
           type = types.nullOr types.lines;
           default = null;
           example = "*               @ALL               8";
-          description = ''
-            Access Control Lists: see <link xlink:href="https://www.dokuwiki.org/acl"/>
+          description = lib.mdDoc ''
+            Access Control Lists: see <https://www.dokuwiki.org/acl>
             Mutually exclusive with services.dokuwiki.aclFile
             Set this to a value other than null to take precedence over aclFile option.
 
@@ -92,11 +91,11 @@ 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;
-          description = ''
+          description = lib.mdDoc ''
             Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl
             Mutually exclusive with services.dokuwiki.acl which is preferred.
-            Consult documentation <link xlink:href="https://www.dokuwiki.org/acl"/> for further instructions.
-            Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist"/>
+            Consult documentation <https://www.dokuwiki.org/acl> for further instructions.
+            Example: <https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist>
           '';
           example = "/var/lib/dokuwiki/${name}/acl.auth.php";
         };
@@ -104,7 +103,7 @@ let
         aclUse = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          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.
@@ -119,7 +118,7 @@ let
             $plugins['authmysql'] = 0;
             $plugins['authpgsql'] = 0;
           '';
-          description = ''
+          description = lib.mdDoc ''
             List of the dokuwiki (un)loaded plugins.
           '';
         };
@@ -127,21 +126,26 @@ let
         superUser = mkOption {
           type = types.nullOr types.str;
           default = "@admin";
-          description = ''
+          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 <link xlink:href="https://www.dokuwiki.org/config:superuser"/> for further instructions.
+            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 = ''
+          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";
         };
@@ -150,9 +154,9 @@ let
           type = types.nullOr types.str;
           default = "";
           example = "search,register";
-          description = ''
+          description = lib.mdDoc ''
             Disable individual action modes. Refer to
-            <link xlink:href="https://www.dokuwiki.org/config:action_modes"/>
+            <https://www.dokuwiki.org/config:action_modes>
             for details on supported values.
           '';
         };
@@ -160,9 +164,12 @@ let
         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
@@ -188,9 +195,12 @@ 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
@@ -222,8 +232,8 @@ let
             "pm.max_spare_servers" = 4;
             "pm.max_requests" = 500;
           };
-          description = ''
-            Options for the DokuWiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+          description = lib.mdDoc ''
+            Options for the DokuWiki PHP pool. See the documentation on `php-fpm.conf`
             for details on configuration directives.
           '';
         };
@@ -235,9 +245,9 @@ let
             $conf['title'] = 'My Wiki';
             $conf['userewrite'] = 1;
           '';
-          description = ''
+          description = lib.mdDoc ''
             DokuWiki configuration. Refer to
-            <link xlink:href="https://www.dokuwiki.org/config"/>
+            <https://www.dokuwiki.org/config>
             for details on supported values.
           '';
         };
@@ -254,20 +264,20 @@ in
       sites = mkOption {
         type = types.attrsOf (types.submodule siteOpts);
         default = {};
-        description = "Specification of one or more DokuWiki sites to serve";
+        description = lib.mdDoc "Specification of one or more DokuWiki sites to serve";
       };
 
       webserver = mkOption {
         type = types.enum [ "nginx" "caddy" ];
         default = "nginx";
-        description = ''
+        description = lib.mdDoc ''
           Whether to use nginx or caddy for virtual host management.
 
-          Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
-          See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+          Further 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 <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
-          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+          Further apache2 configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
+          See [](#opt-services.httpd.virtualHosts) for further information.
         '';
       };
 
@@ -293,9 +303,7 @@ in
         inherit user;
         group = webserver.group;
 
-        # Not yet compatible with php 8 https://www.dokuwiki.org/requirements
-        # https://github.com/splitbrain/dokuwiki/issues/3545
-        phpPackage = pkgs.php74;
+        phpPackage = pkgs.php81;
         phpEnv = {
           DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig hostName cfg}";
           DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig hostName cfg}";
@@ -316,16 +324,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);
@@ -349,7 +358,7 @@ in
           };
 
           "~ ^/data/" = {
-            root = "${stateDir hostName}";
+            root = "${cfg.stateDir}";
             extraConfig = "internal;";
           };
 
diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix
new file mode 100644
index 000000000000..f262099354d2
--- /dev/null
+++ b/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.optionalAttr (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 isNull v 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/nixos/modules/services/web-apps/engelsystem.nix b/nixos/modules/services/web-apps/engelsystem.nix
index 06c3c6dfc3d7..f1d71f174471 100644
--- a/nixos/modules/services/web-apps/engelsystem.nix
+++ b/nixos/modules/services/web-apps/engelsystem.nix
@@ -9,7 +9,7 @@ in {
       enable = mkOption {
         default = false;
         example = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable engelsystem, an online tool for coordinating volunteers
           and shifts on large events.
         '';
@@ -19,12 +19,12 @@ in {
       domain = mkOption {
         type = types.str;
         example = "engelsystem.example.com";
-        description = "Domain to serve on.";
+        description = lib.mdDoc "Domain to serve on.";
       };
 
       package = mkOption {
         type = types.package;
-        description = "Engelsystem package used for the service.";
+        description = lib.mdDoc "Engelsystem package used for the service.";
         default = pkgs.engelsystem;
         defaultText = literalExpression "pkgs.engelsystem";
       };
@@ -32,9 +32,9 @@ in {
       createDatabase = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create a local database automatically.
-          This will override every database setting in <option>services.engelsystem.config</option>.
+          This will override every database setting in {option}`services.engelsystem.config`.
         '';
       };
     };
@@ -70,7 +70,7 @@ in {
         min_password_length = 6;
         default_locale = "de_DE";
       };
-      description = ''
+      description = lib.mdDoc ''
         Options to be added to config.php, as a nix attribute set. 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
diff --git a/nixos/modules/services/web-apps/ethercalc.nix b/nixos/modules/services/web-apps/ethercalc.nix
index d74def59c6c3..a5be86a34aa6 100644
--- a/nixos/modules/services/web-apps/ethercalc.nix
+++ b/nixos/modules/services/web-apps/ethercalc.nix
@@ -10,11 +10,11 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           ethercalc, an online collaborative spreadsheet server.
 
           Persistent state will be maintained under
-          <filename>/var/lib/ethercalc</filename>. Upstream supports using a
+          {file}`/var/lib/ethercalc`. Upstream supports using a
           redis server for storage and recommends the redis backend for
           intensive use; however, the Nix module doesn't currently support
           redis.
@@ -28,19 +28,19 @@ in {
         default = pkgs.ethercalc;
         defaultText = literalExpression "pkgs.ethercalc";
         type = types.package;
-        description = "Ethercalc package to use.";
+        description = lib.mdDoc "Ethercalc package to use.";
       };
 
       host = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "Address to listen on (use 0.0.0.0 to allow access from any address).";
+        description = lib.mdDoc "Address to listen on (use 0.0.0.0 to allow access from any address).";
       };
 
       port = mkOption {
         type = types.port;
         default = 8000;
-        description = "Port to bind to.";
+        description = lib.mdDoc "Port to bind to.";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/fluidd.nix b/nixos/modules/services/web-apps/fluidd.nix
index 6ac1acc9d036..d4b86b9dfb39 100644
--- a/nixos/modules/services/web-apps/fluidd.nix
+++ b/nixos/modules/services/web-apps/fluidd.nix
@@ -6,11 +6,11 @@ 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;
-      description = "Fluidd package to be used in the module";
+      description = lib.mdDoc "Fluidd package to be used in the module";
       default = pkgs.fluidd;
       defaultText = literalExpression "pkgs.fluidd";
     };
@@ -18,7 +18,7 @@ in
     hostName = mkOption {
       type = types.str;
       default = "localhost";
-      description = "Hostname to serve fluidd on";
+      description = lib.mdDoc "Hostname to serve fluidd on";
     };
 
     nginx = mkOption {
@@ -30,7 +30,7 @@ in
           serverAliases = [ "fluidd.''${config.networking.domain}" ];
         }
       '';
-      description = "Extra configuration for the nginx virtual host of fluidd.";
+      description = lib.mdDoc "Extra configuration for the nginx virtual host of fluidd.";
     };
   };
 
diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix
new file mode 100644
index 000000000000..c05e7b2c4f7f
--- /dev/null
+++ b/nixos/modules/services/web-apps/freshrss.nix
@@ -0,0 +1,282 @@
+{ 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 = with types; nullOr 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.str;
+        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.
+      '';
+    };
+  };
+
+
+  config =
+    let
+      systemd-hardening = {
+        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";
+      };
+    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.freshrss = {
+        description = "FreshRSS service user";
+        isSystemUser = true;
+        group = "freshrss";
+      };
+      users.groups.freshrss = { };
+
+      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 = {
+            Type = "oneshot";
+            User = "freshrss";
+            Group = "freshrss";
+            StateDirectory = "freshrss";
+            WorkingDirectory = cfg.package;
+          } // systemd-hardening;
+          environment = {
+            FRESHRSS_DATA_PATH = cfg.dataDir;
+          };
+
+          script = ''
+            # create files with correct permissions
+            mkdir -m 755 -p ${cfg.dataDir}
+
+            # 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
+              # Copy the user data template directory
+              cp -r ./data ${cfg.dataDir}
+
+              # 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 = {
+          Type = "oneshot";
+          User = "freshrss";
+          Group = "freshrss";
+          StateDirectory = "freshrss";
+          WorkingDirectory = cfg.package;
+          ExecStart = "${cfg.package}/app/actualize_script.php";
+        } // systemd-hardening;
+      };
+    };
+}
diff --git a/nixos/modules/services/web-apps/galene.nix b/nixos/modules/services/web-apps/galene.nix
index 1d0a620585b0..15ef09aa0b87 100644
--- a/nixos/modules/services/web-apps/galene.nix
+++ b/nixos/modules/services/web-apps/galene.nix
@@ -12,12 +12,12 @@ in
 {
   options = {
     services.galene = {
-      enable = mkEnableOption "Galene Service.";
+      enable = mkEnableOption (lib.mdDoc "Galene Service.");
 
       stateDir = mkOption {
         default = defaultstateDir;
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The directory where Galene stores its internal state. If left as the default
           value this directory will automatically be created before the Galene server
           starts, otherwise the sysadmin is responsible for ensuring the directory
@@ -28,19 +28,19 @@ in
       user = mkOption {
         type = types.str;
         default = "galene";
-        description = "User account under which galene runs.";
+        description = lib.mdDoc "User account under which galene runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "galene";
-        description = "Group under which galene runs.";
+        description = lib.mdDoc "Group under which galene runs.";
       };
 
       insecure = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether Galene should listen in http or in https. If left as the default
           value (false), Galene needs to be fed a private key and a certificate.
         '';
@@ -50,7 +50,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "/path/to/your/cert.pem";
-        description = ''
+        description = lib.mdDoc ''
           Path to the server's certificate. The file is copied at runtime to
           Galene's data directory where it needs to reside.
         '';
@@ -60,7 +60,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "/path/to/your/key.pem";
-        description = ''
+        description = lib.mdDoc ''
           Path to the server's private key. The file is copied at runtime to
           Galene's data directory where it needs to reside.
         '';
@@ -69,13 +69,13 @@ in
       httpAddress = mkOption {
         type = types.str;
         default = "";
-        description = "HTTP listen address for galene.";
+        description = lib.mdDoc "HTTP listen address for galene.";
       };
 
       httpPort = mkOption {
         type = types.port;
         default = 8443;
-        description = "HTTP listen port.";
+        description = lib.mdDoc "HTTP listen port.";
       };
 
       staticDir = mkOption {
@@ -83,7 +83,7 @@ in
         default = "${cfg.package.static}/static";
         defaultText = literalExpression ''"''${package.static}/static"'';
         example = "/var/lib/galene/static";
-        description = "Web server directory.";
+        description = lib.mdDoc "Web server directory.";
       };
 
       recordingsDir = mkOption {
@@ -91,7 +91,7 @@ in
         default = defaultrecordingsDir;
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/recordings"'';
         example = "/var/lib/galene/recordings";
-        description = "Recordings directory.";
+        description = lib.mdDoc "Recordings directory.";
       };
 
       dataDir = mkOption {
@@ -99,7 +99,7 @@ in
         default = defaultdataDir;
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/data"'';
         example = "/var/lib/galene/data";
-        description = "Data directory.";
+        description = lib.mdDoc "Data directory.";
       };
 
       groupsDir = mkOption {
@@ -107,14 +107,14 @@ in
         default = defaultgroupsDir;
         defaultText = literalExpression ''"''${config.${opt.stateDir}}/groups"'';
         example = "/var/lib/galene/groups";
-        description = "Web server directory.";
+        description = lib.mdDoc "Web server directory.";
       };
 
       package = mkOption {
         default = pkgs.galene;
         defaultText = literalExpression "pkgs.galene";
         type = types.package;
-        description = ''
+        description = lib.mdDoc ''
           Package for running Galene.
         '';
       };
@@ -164,6 +164,35 @@ in
             optional (cfg.dataDir == defaultdataDir) "galene/data" ++
             optional (cfg.groupsDir == defaultgroupsDir) "galene/groups" ++
             optional (cfg.recordingsDir == defaultrecordingsDir) "galene/recordings";
+
+          # 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.recordingsDir;
+          RemoveIPC = true;
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [ "@system-service" "~@privileged" ];
+          UMask = "0077";
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/gerrit.nix b/nixos/modules/services/web-apps/gerrit.nix
index 6bfc67368dd5..ab2eeea09bdc 100644
--- a/nixos/modules/services/web-apps/gerrit.nix
+++ b/nixos/modules/services/web-apps/gerrit.nix
@@ -59,20 +59,20 @@ in
 {
   options = {
     services.gerrit = {
-      enable = mkEnableOption "Gerrit service";
+      enable = mkEnableOption (lib.mdDoc "Gerrit service");
 
       package = mkOption {
         type = types.package;
         default = pkgs.gerrit;
         defaultText = literalExpression "pkgs.gerrit";
-        description = "Gerrit package to use";
+        description = lib.mdDoc "Gerrit package to use";
       };
 
       jvmPackage = mkOption {
         type = types.package;
         default = pkgs.jre_headless;
         defaultText = literalExpression "pkgs.jre_headless";
-        description = "Java Runtime Environment package to use";
+        description = lib.mdDoc "Java Runtime Environment package to use";
       };
 
       jvmOpts = mkOption {
@@ -81,13 +81,13 @@ in
           "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
           "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
         ];
-        description = "A list of JVM options to start gerrit with.";
+        description = lib.mdDoc "A list of JVM options to start gerrit with.";
       };
 
       jvmHeapLimit = mkOption {
         type = types.str;
         default = "1024m";
-        description = ''
+        description = lib.mdDoc ''
           How much memory to allocate to the JVM heap
         '';
       };
@@ -95,8 +95,8 @@ in
       listenAddress = mkOption {
         type = types.str;
         default = "[::]:8080";
-        description = ''
-          <literal>hostname:port</literal> to listen for HTTP traffic.
+        description = lib.mdDoc ''
+          `hostname:port` to listen for HTTP traffic.
 
           This is bound using the systemd socket activation.
         '';
@@ -105,25 +105,25 @@ in
       settings = mkOption {
         type = gitIniType;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Gerrit configuration. This will be generated to the
-          <literal>etc/gerrit.config</literal> file.
+          `etc/gerrit.config` file.
         '';
       };
 
       replicationSettings = mkOption {
         type = gitIniType;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Replication configuration. This will be generated to the
-          <literal>etc/replication.config</literal> file.
+          `etc/replication.config` file.
         '';
       };
 
       plugins = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of plugins to add to Gerrit. Each derivation is a jar file
           itself where the name of the derivation is the name of plugin.
         '';
@@ -132,19 +132,19 @@ in
       builtinPlugins = mkOption {
         type = types.listOf (types.enum cfg.package.passthru.plugins);
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of builtins plugins to install. Those are shipped in the
-          <literal>gerrit.war</literal> file.
+          `gerrit.war` file.
         '';
       };
 
       serverId = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Set a UUID that uniquely identifies the server.
 
           This can be generated with
-          <literal>nix-shell -p util-linux --run uuidgen</literal>.
+          `nix-shell -p util-linux --run uuidgen`.
         '';
       };
     };
diff --git a/nixos/modules/services/web-apps/gotify-server.nix b/nixos/modules/services/web-apps/gotify-server.nix
index 03e01f46a944..8db3a8ef3e81 100644
--- a/nixos/modules/services/web-apps/gotify-server.nix
+++ b/nixos/modules/services/web-apps/gotify-server.nix
@@ -7,11 +7,11 @@ let
 in {
   options = {
     services.gotify = {
-      enable = mkEnableOption "Gotify webserver";
+      enable = mkEnableOption (lib.mdDoc "Gotify webserver");
 
       port = mkOption {
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           Port the server listens to.
         '';
       };
@@ -19,8 +19,8 @@ in {
       stateDirectoryName = mkOption {
         type = types.str;
         default = "gotify-server";
-        description = ''
-          The name of the directory below <filename>/var/lib</filename> where
+        description = lib.mdDoc ''
+          The name of the directory below {file}`/var/lib` where
           gotify stores its runtime data.
         '';
       };
diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix
index be2de638dd96..6efc2ccfd302 100644
--- a/nixos/modules/services/web-apps/grocy.nix
+++ b/nixos/modules/services/web-apps/grocy.nix
@@ -6,11 +6,11 @@ let
   cfg = config.services.grocy;
 in {
   options.services.grocy = {
-    enable = mkEnableOption "grocy";
+    enable = mkEnableOption (lib.mdDoc "grocy");
 
     hostName = mkOption {
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         FQDN for the grocy instance.
       '';
     };
@@ -18,7 +18,7 @@ in {
     nginx.enableSSL = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether or not to enable SSL (with ACME and let's encrypt)
         for the grocy vhost.
       '';
@@ -39,7 +39,7 @@ in {
         "pm.max_requests" = "500";
       };
 
-      description = ''
+      description = lib.mdDoc ''
         Options for grocy's PHPFPM pool.
       '';
     };
@@ -47,8 +47,8 @@ in {
     dataDir = mkOption {
       type = types.str;
       default = "/var/lib/grocy";
-      description = ''
-        Home directory of the <literal>grocy</literal> user which contains
+      description = lib.mdDoc ''
+        Home directory of the `grocy` user which contains
         the application's state.
       '';
     };
@@ -58,7 +58,7 @@ in {
         type = types.str;
         default = "USD";
         example = "EUR";
-        description = ''
+        description = lib.mdDoc ''
           ISO 4217 code for the currency to display.
         '';
       };
@@ -66,7 +66,7 @@ in {
       culture = mkOption {
         type = types.enum [ "de" "en" "da" "en_GB" "es" "fr" "hu" "it" "nl" "no" "pl" "pt_BR" "ru" "sk_SK" "sv_SE" "tr" ];
         default = "en";
-        description = ''
+        description = lib.mdDoc ''
           Display language of the frontend.
         '';
       };
@@ -75,14 +75,14 @@ in {
         showWeekNumber = mkOption {
           default = true;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Show the number of the weeks in the calendar views.
           '';
         };
         firstDayOfWeek = mkOption {
           default = null;
           type = types.nullOr (types.enum (range 0 6));
-          description = ''
+          description = lib.mdDoc ''
             Which day of the week (0=Sunday, 1=Monday etc.) should be the
             first day.
           '';
@@ -115,9 +115,9 @@ in {
       user = "grocy";
       group = "nginx";
 
-      # PHP 7.4 is the only version which is supported/tested by upstream:
-      # https://github.com/grocy/grocy/blob/v3.0.0/README.md#how-to-install
-      phpPackage = pkgs.php74;
+      # 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;
 
       inherit (cfg.phpfpm) settings;
 
diff --git a/nixos/modules/services/web-apps/healthchecks.nix b/nixos/modules/services/web-apps/healthchecks.nix
new file mode 100644
index 000000000000..b3fdb681e2f3
--- /dev/null
+++ b/nixos/modules/services/web-apps/healthchecks.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, buildEnv, ... }:
+
+with lib;
+
+let
+  defaultUser = "healthchecks";
+  cfg = config.services.healthchecks;
+  pkg = cfg.package;
+  boolToPython = b: if b then "True" else "False";
+  environment = {
+    PYTHONPATH = pkg.pythonPath;
+    STATIC_ROOT = cfg.dataDir + "/static";
+    DB_NAME = "${cfg.dataDir}/healthchecks.sqlite";
+  } // cfg.settings;
+
+  environmentFile = pkgs.writeText "healthchecks-environment" (lib.generators.toKeyValue { } environment);
+
+  healthchecksManageScript = pkgs.writeShellScriptBin "healthchecks-manage" ''
+    sudo=exec
+    if [[ "$USER" != "${cfg.user}" ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env --preserve-env=PYTHONPATH'
+    fi
+    export $(cat ${environmentFile} | xargs)
+    $sudo ${pkg}/opt/healthchecks/manage.py "$@"
+  '';
+in
+{
+  options.services.healthchecks = {
+    enable = mkEnableOption (lib.mdDoc "healthchecks") // {
+      description = lib.mdDoc ''
+        Enable healthchecks.
+        It is expected to be run behind a HTTP reverse proxy.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.healthchecks;
+      defaultText = literalExpression "pkgs.healthchecks";
+      type = types.package;
+      description = lib.mdDoc "healthchecks package to use.";
+    };
+
+    user = mkOption {
+      default = defaultUser;
+      type = types.str;
+      description = lib.mdDoc ''
+        User account under which healthchecks 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 healthchecks service starts.
+        :::
+      '';
+    };
+
+    group = mkOption {
+      default = defaultUser;
+      type = types.str;
+      description = lib.mdDoc ''
+        Group account under which healthchecks 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 healthchecks service starts.
+        :::
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc "Address the server will listen on.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8000;
+      description = lib.mdDoc "Port the server will listen on.";
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/healthchecks";
+      description = lib.mdDoc ''
+        The directory used to store all data for healthchecks.
+
+        ::: {.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.
+        :::
+      '';
+    };
+
+    settings = lib.mkOption {
+      description = lib.mdDoc ''
+        Environment variables which are read by healthchecks `(local)_settings.py`.
+
+        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 <https://healthchecks.io/docs/self_hosted_configuration/>
+        for a full documentation of settings.
+
+        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.
+      '';
+      type = types.submodule {
+        freeformType = types.attrsOf types.str;
+        options = {
+          ALLOWED_HOSTS = lib.mkOption {
+            type = types.listOf types.str;
+            default = [ "*" ];
+            description = lib.mdDoc "The host/domain names that this site can serve.";
+            apply = lib.concatStringsSep ",";
+          };
+
+          SECRET_KEY_FILE = mkOption {
+            type = types.path;
+            description = lib.mdDoc "Path to a file containing the secret key.";
+          };
+
+          DEBUG = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Enable debug mode.";
+            apply = boolToPython;
+          };
+
+          REGISTRATION_OPEN = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc ''
+              A boolean that controls whether site visitors can create new accounts.
+              Set it to false if you are setting up a private Healthchecks instance,
+              but it needs to be publicly accessible (so, for example, your cloud
+              services can send pings to it).
+              If you close new user registration, you can still selectively invite
+              users to your team account.
+            '';
+            apply = boolToPython;
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ healthchecksManageScript ];
+
+    systemd.targets.healthchecks = {
+      description = "Target for all Healthchecks services";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "network-online.target" ];
+    };
+
+    systemd.services =
+      let
+        commonConfig = {
+          WorkingDirectory = cfg.dataDir;
+          User = cfg.user;
+          Group = cfg.group;
+          EnvironmentFile = [ environmentFile ];
+          StateDirectory = mkIf (cfg.dataDir == "/var/lib/healthchecks") "healthchecks";
+          StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750";
+        };
+      in
+        {
+        healthchecks-migration = {
+          description = "Healthchecks migrations";
+          wantedBy = [ "healthchecks.target" ];
+
+          serviceConfig = commonConfig // {
+            Restart = "on-failure";
+            Type = "oneshot";
+            ExecStart = ''
+              ${pkg}/opt/healthchecks/manage.py migrate
+            '';
+          };
+        };
+
+        healthchecks = {
+          description = "Healthchecks WSGI Service";
+          wantedBy = [ "healthchecks.target" ];
+          after = [ "healthchecks-migration.service" ];
+
+          preStart = ''
+            ${pkg}/opt/healthchecks/manage.py collectstatic --no-input
+            ${pkg}/opt/healthchecks/manage.py remove_stale_contenttypes --no-input
+            ${pkg}/opt/healthchecks/manage.py compress
+          '';
+
+          serviceConfig = commonConfig // {
+            Restart = "always";
+            ExecStart = ''
+              ${pkgs.python3Packages.gunicorn}/bin/gunicorn hc.wsgi \
+                --bind ${cfg.listenAddress}:${toString cfg.port} \
+                --pythonpath ${pkg}/opt/healthchecks
+            '';
+          };
+        };
+
+        healthchecks-sendalerts = {
+          description = "Healthchecks Alert Service";
+          wantedBy = [ "healthchecks.target" ];
+          after = [ "healthchecks.service" ];
+
+          serviceConfig = commonConfig // {
+            Restart = "always";
+            ExecStart = ''
+              ${pkg}/opt/healthchecks/manage.py sendalerts
+            '';
+          };
+        };
+
+        healthchecks-sendreports = {
+          description = "Healthchecks Reporting Service";
+          wantedBy = [ "healthchecks.target" ];
+          after = [ "healthchecks.service" ];
+
+          serviceConfig = commonConfig // {
+            Restart = "always";
+            ExecStart = ''
+              ${pkg}/opt/healthchecks/manage.py sendreports --loop
+            '';
+          };
+        };
+      };
+
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} =
+        {
+          description = "healthchecks service owner";
+          isSystemUser = true;
+          group = defaultUser;
+        };
+    };
+
+    users.groups = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} =
+        {
+          members = [ defaultUser ];
+        };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix
index 9eeabb9d5662..a623e45691df 100644
--- a/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixos/modules/services/web-apps/hedgedoc.nix
@@ -13,26 +13,31 @@ let
     then "hedgedoc"
     else "codimd";
 
+  settingsFormat = pkgs.formats.json {};
+
   prettyJSON = conf:
     pkgs.runCommandLocal "hedgedoc-config.json" {
       nativeBuildInputs = [ pkgs.jq ];
     } ''
-      echo '${builtins.toJSON conf}' | jq \
-        '{production:del(.[]|nulls)|del(.[][]?|nulls)}' > $out
+      jq '{production:del(.[]|nulls)|del(.[][]?|nulls)}' \
+        < ${settingsFormat.generate "hedgedoc-ugly.json" cfg.settings} \
+        > $out
     '';
 in
 {
   imports = [
     (mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
+    (mkRenamedOptionModule
+      [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
   ];
 
   options.services.hedgedoc = {
-    enable = mkEnableOption "the HedgeDoc Markdown Editor";
+    enable = mkEnableOption (lib.mdDoc "the HedgeDoc Markdown Editor");
 
     groups = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Groups to which the service user should be added.
       '';
     };
@@ -40,18 +45,18 @@ in
     workDir = mkOption {
       type = types.path;
       default = "/var/lib/${name}";
-      description = ''
+      description = lib.mdDoc ''
         Working directory for the HedgeDoc service.
       '';
     };
 
-    configuration = {
-      debug = mkEnableOption "debug mode";
+    settings = let options = {
+      debug = mkEnableOption (lib.mdDoc "debug mode");
       domain = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "hedgedoc.org";
-        description = ''
+        description = lib.mdDoc ''
           Domain name for the HedgeDoc instance.
         '';
       };
@@ -59,22 +64,22 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "/url/path/to/hedgedoc";
-        description = ''
+        description = lib.mdDoc ''
           Path under which HedgeDoc is accessible.
         '';
       };
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           Address to listen on.
         '';
       };
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = 3000;
         example = 80;
-        description = ''
+        description = lib.mdDoc ''
           Port to listen on.
         '';
       };
@@ -82,7 +87,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "/run/hedgedoc.sock";
-        description = ''
+        description = lib.mdDoc ''
           Specify where a UNIX domain socket should be placed.
         '';
       };
@@ -90,44 +95,44 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "localhost" "hedgedoc.org" ];
-        description = ''
+        description = lib.mdDoc ''
           List of domains to whitelist.
         '';
       };
       useSSL = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable to use SSL server. This will also enable
-          <option>protocolUseSSL</option>.
+          {option}`protocolUseSSL`.
         '';
       };
       hsts = {
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to enable HSTS if HTTPS is also enabled.
           '';
         };
         maxAgeSeconds = mkOption {
           type = types.int;
           default = 31536000;
-          description = ''
+          description = lib.mdDoc ''
             Max duration for clients to keep the HSTS status.
           '';
         };
         includeSubdomains = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to include subdomains in HSTS.
           '';
         };
         preload = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether to allow preloading of the site's HSTS status.
           '';
         };
@@ -145,62 +150,68 @@ in
             addDefaults = true;
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Specify the Content Security Policy which is passed to Helmet.
-          For configuration details see <link xlink:href="https://helmetjs.github.io/docs/csp/"
-          >https://helmetjs.github.io/docs/csp/</link>.
+          For configuration details see <https://helmetjs.github.io/docs/csp/>.
         '';
       };
       protocolUseSSL = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable to use TLS for resource paths.
-          This only applies when <option>domain</option> is set.
+          This only applies when {option}`domain` is set.
         '';
       };
       urlAddPort = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable to add the port to callback URLs.
-          This only applies when <option>domain</option> is set
+          This only applies when {option}`domain` is set
           and only for ports other than 80 and 443.
         '';
       };
       useCDN = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use CDN resources or not.
         '';
       };
       allowAnonymous = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow anonymous usage.
         '';
       };
       allowAnonymousEdits = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Whether to allow guests to edit existing notes with the `freely' permission,
-          when <option>allowAnonymous</option> is enabled.
+        description = lib.mdDoc ''
+          Whether to allow guests to edit existing notes with the `freely` permission,
+          when {option}`allowAnonymous` is enabled.
         '';
       };
       allowFreeURL = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow note creation by accessing a nonexistent note URL.
         '';
       };
+      requireFreeURLAuthentication = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to require authentication for FreeURL mode style note creation.
+        '';
+      };
       defaultPermission = mkOption {
         type = types.enum [ "freely" "editable" "limited" "locked" "private" ];
         default = "editable";
-        description = ''
+        description = lib.mdDoc ''
           Default permissions for notes.
           This only applies for signed-in users.
         '';
@@ -211,12 +222,12 @@ in
         example = ''
           postgres://user:pass@host:5432/dbname
         '';
-        description = ''
+        description = lib.mdDoc ''
           Specify which database to use.
           HedgeDoc supports mysql, postgres, sqlite and mssql.
-          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
-          https://sequelize.readthedocs.io/en/v3/</link> for more information.
-          Note: This option overrides <option>db</option>.
+          See [
+          https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
+          Note: This option overrides {option}`db`.
         '';
       };
       db = mkOption {
@@ -228,52 +239,52 @@ in
             storage = "/var/lib/${name}/db.${name}.sqlite";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Specify the configuration for sequelize.
           HedgeDoc supports mysql, postgres, sqlite and mssql.
-          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
-          https://sequelize.readthedocs.io/en/v3/</link> for more information.
-          Note: This option overrides <option>db</option>.
+          See [
+          https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
+          Note: This option overrides {option}`db`.
         '';
       };
       sslKeyPath= mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/var/lib/hedgedoc/hedgedoc.key";
-        description = ''
-          Path to the SSL key. Needed when <option>useSSL</option> is enabled.
+        description = lib.mdDoc ''
+          Path to the SSL key. Needed when {option}`useSSL` is enabled.
         '';
       };
       sslCertPath = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/var/lib/hedgedoc/hedgedoc.crt";
-        description = ''
-          Path to the SSL cert. Needed when <option>useSSL</option> is enabled.
+        description = lib.mdDoc ''
+          Path to the SSL cert. Needed when {option}`useSSL` is enabled.
         '';
       };
       sslCAPath = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "/var/lib/hedgedoc/ca.crt" ];
-        description = ''
-          SSL ca chain. Needed when <option>useSSL</option> is enabled.
+        description = lib.mdDoc ''
+          SSL ca chain. Needed when {option}`useSSL` is enabled.
         '';
       };
       dhParamPath = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "/var/lib/hedgedoc/dhparam.pem";
-        description = ''
-          Path to the SSL dh params. Needed when <option>useSSL</option> is enabled.
+        description = lib.mdDoc ''
+          Path to the SSL dh params. Needed when {option}`useSSL` is enabled.
         '';
       };
       tmpPath = mkOption {
         type = types.str;
         default = "/tmp";
-        description = ''
+        description = lib.mdDoc ''
           Path to the temp directory HedgeDoc should use.
-          Note that <option>serviceConfig.PrivateTmp</option> is enabled for
+          Note that {option}`serviceConfig.PrivateTmp` is enabled for
           the HedgeDoc systemd service by default.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -281,7 +292,7 @@ in
       defaultNotePath = mkOption {
         type = types.nullOr types.str;
         default = "./public/default.md";
-        description = ''
+        description = lib.mdDoc ''
           Path to the default Note file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -289,7 +300,7 @@ in
       docsPath = mkOption {
         type = types.nullOr types.str;
         default = "./public/docs";
-        description = ''
+        description = lib.mdDoc ''
           Path to the docs directory.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -297,7 +308,7 @@ in
       indexPath = mkOption {
         type = types.nullOr types.str;
         default = "./public/views/index.ejs";
-        description = ''
+        description = lib.mdDoc ''
           Path to the index template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -305,7 +316,7 @@ in
       hackmdPath = mkOption {
         type = types.nullOr types.str;
         default = "./public/views/hackmd.ejs";
-        description = ''
+        description = lib.mdDoc ''
           Path to the hackmd template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -314,7 +325,7 @@ in
         type = types.nullOr types.str;
         default = null;
         defaultText = literalExpression "./public/views/error.ejs";
-        description = ''
+        description = lib.mdDoc ''
           Path to the error template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -323,7 +334,7 @@ in
         type = types.nullOr types.str;
         default = null;
         defaultText = literalExpression "./public/views/pretty.ejs";
-        description = ''
+        description = lib.mdDoc ''
           Path to the pretty template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -332,7 +343,7 @@ in
         type = types.nullOr types.str;
         default = null;
         defaultText = literalExpression "./public/views/slide.hbs";
-        description = ''
+        description = lib.mdDoc ''
           Path to the slide template file.
           (Non-canonical paths are relative to HedgeDoc's base directory)
         '';
@@ -341,21 +352,21 @@ in
         type = types.str;
         default = "${cfg.workDir}/uploads";
         defaultText = literalExpression "/var/lib/${name}/uploads";
-        description = ''
+        description = lib.mdDoc ''
           Path under which uploaded files are saved.
         '';
       };
       sessionName = mkOption {
         type = types.str;
         default = "connect.sid";
-        description = ''
+        description = lib.mdDoc ''
           Specify the name of the session cookie.
         '';
       };
       sessionSecret = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Specify the secret used to sign the session cookie.
           If unset, one will be generated on startup.
         '';
@@ -363,56 +374,56 @@ in
       sessionLife = mkOption {
         type = types.int;
         default = 1209600000;
-        description = ''
+        description = lib.mdDoc ''
           Session life time in milliseconds.
         '';
       };
       heartbeatInterval = mkOption {
         type = types.int;
         default = 5000;
-        description = ''
+        description = lib.mdDoc ''
           Specify the socket.io heartbeat interval.
         '';
       };
       heartbeatTimeout = mkOption {
         type = types.int;
         default = 10000;
-        description = ''
+        description = lib.mdDoc ''
           Specify the socket.io heartbeat timeout.
         '';
       };
       documentMaxLength = mkOption {
         type = types.int;
         default = 100000;
-        description = ''
+        description = lib.mdDoc ''
           Specify the maximum document length.
         '';
       };
       email = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable email sign-in.
         '';
       };
       allowEmailRegister = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable email registration.
         '';
       };
       allowGravatar = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to use gravatar as profile picture source.
         '';
       };
       imageUploadType = mkOption {
         type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
         default = "filesystem";
-        description = ''
+        description = lib.mdDoc ''
           Specify where to upload images.
         '';
       };
@@ -421,85 +432,85 @@ in
           options = {
             accessKey = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Minio access key.
               '';
             };
             secretKey = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Minio secret key.
               '';
             };
-            endpoint = mkOption {
+            endPoint = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Minio endpoint.
               '';
             };
             port = mkOption {
-              type = types.int;
+              type = types.port;
               default = 9000;
-              description = ''
+              description = lib.mdDoc ''
                 Minio listen port.
               '';
             };
             secure = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to use HTTPS for Minio.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the minio third-party integration.";
+        description = lib.mdDoc "Configure the minio third-party integration.";
       };
       s3 = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             accessKeyId = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 AWS access key id.
               '';
             };
             secretAccessKey = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 AWS access key.
               '';
             };
             region = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 AWS S3 region.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the s3 third-party integration.";
+        description = lib.mdDoc "Configure the s3 third-party integration.";
       };
       s3bucket = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
-          Specify the bucket name for upload types <literal>s3</literal> and <literal>minio</literal>.
+        description = lib.mdDoc ''
+          Specify the bucket name for upload types `s3` and `minio`.
         '';
       };
       allowPDFExport = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable PDF exports.
         '';
       };
       imgur.clientId = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Imgur API client ID.
         '';
       };
@@ -508,13 +519,13 @@ in
           options = {
             connectionString = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Azure Blob Storage connection string.
               '';
             };
             container = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Azure Blob Storage container name.
                 It will be created if non-existent.
               '';
@@ -522,162 +533,162 @@ in
           };
         });
         default = null;
-        description = "Configure the azure third-party integration.";
+        description = lib.mdDoc "Configure the azure third-party integration.";
       };
       oauth2 = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             authorizationURL = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the OAuth authorization URL.
               '';
             };
             tokenURL = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the OAuth token URL.
               '';
             };
             baseURL = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the OAuth base URL.
               '';
             };
             userProfileURL = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the OAuth userprofile URL.
               '';
             };
             userProfileUsernameAttr = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the name of the attribute for the username from the claim.
               '';
             };
             userProfileDisplayNameAttr = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the name of the attribute for the display name from the claim.
               '';
             };
             userProfileEmailAttr = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the name of the attribute for the email from the claim.
               '';
             };
             scope = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the OAuth scope.
               '';
             };
             providerName = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the name to be displayed for this strategy.
               '';
             };
             rolesClaim = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the role claim name.
               '';
             };
             accessRole = mkOption {
               type = with types; nullOr str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 Specify role which should be included in the ID token roles claim to grant access
               '';
             };
             clientID = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the OAuth client ID.
               '';
             };
             clientSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Specify the OAuth client secret.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the OAuth integration.";
+        description = lib.mdDoc "Configure the OAuth integration.";
       };
       facebook = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             clientID = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Facebook API client ID.
               '';
             };
             clientSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Facebook API client secret.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the facebook third-party integration";
+        description = lib.mdDoc "Configure the facebook third-party integration";
       };
       twitter = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             consumerKey = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Twitter API consumer key.
               '';
             };
             consumerSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Twitter API consumer secret.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the Twitter third-party integration.";
+        description = lib.mdDoc "Configure the Twitter third-party integration.";
       };
       github = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             clientID = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 GitHub API client ID.
               '';
             };
             clientSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Github API client secret.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the GitHub third-party integration.";
+        description = lib.mdDoc "Configure the GitHub third-party integration.";
       };
       gitlab = mkOption {
         type = types.nullOr (types.submodule {
@@ -685,27 +696,27 @@ in
             baseURL = mkOption {
               type = types.str;
               default = "";
-              description = ''
+              description = lib.mdDoc ''
                 GitLab API authentication endpoint.
                 Only needed for other endpoints than gitlab.com.
               '';
             };
             clientID = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 GitLab API client ID.
               '';
             };
             clientSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 GitLab API client secret.
               '';
             };
             scope = mkOption {
               type = types.enum [ "api" "read_user" ];
               default = "api";
-              description = ''
+              description = lib.mdDoc ''
                 GitLab API requested scope.
                 GitLab snippet import/export requires api scope.
               '';
@@ -713,79 +724,79 @@ in
           };
         });
         default = null;
-        description = "Configure the GitLab third-party integration.";
+        description = lib.mdDoc "Configure the GitLab third-party integration.";
       };
       mattermost = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             baseURL = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Mattermost authentication endpoint.
               '';
             };
             clientID = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Mattermost API client ID.
               '';
             };
             clientSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Mattermost API client secret.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the Mattermost third-party integration.";
+        description = lib.mdDoc "Configure the Mattermost third-party integration.";
       };
       dropbox = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             clientID = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Dropbox API client ID.
               '';
             };
             clientSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Dropbox API client secret.
               '';
             };
             appKey = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Dropbox app key.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the Dropbox third-party integration.";
+        description = lib.mdDoc "Configure the Dropbox third-party integration.";
       };
       google = mkOption {
         type = types.nullOr (types.submodule {
           options = {
             clientID = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Google API client ID.
               '';
             };
             clientSecret = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Google API client secret.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the Google third-party integration.";
+        description = lib.mdDoc "Configure the Google third-party integration.";
       };
       ldap = mkOption {
         type = types.nullOr (types.submodule {
@@ -793,76 +804,78 @@ in
             providerName = mkOption {
               type = types.str;
               default = "";
-              description = ''
+              description = lib.mdDoc ''
                 Optional name to be displayed at login form, indicating the LDAP provider.
               '';
             };
             url = mkOption {
               type = types.str;
               example = "ldap://localhost";
-              description = ''
+              description = lib.mdDoc ''
                 URL of LDAP server.
               '';
             };
             bindDn = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Bind DN for LDAP access.
               '';
             };
             bindCredentials = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Bind credentials for LDAP access.
               '';
             };
             searchBase = mkOption {
               type = types.str;
               example = "o=users,dc=example,dc=com";
-              description = ''
+              description = lib.mdDoc ''
                 LDAP directory to begin search from.
               '';
             };
             searchFilter = mkOption {
               type = types.str;
               example = "(uid={{username}})";
-              description = ''
+              description = lib.mdDoc ''
                 LDAP filter to search with.
               '';
             };
             searchAttributes = mkOption {
-              type = types.listOf types.str;
+              type = types.nullOr (types.listOf types.str);
+              default = null;
               example = [ "displayName" "mail" ];
-              description = ''
+              description = lib.mdDoc ''
                 LDAP attributes to search with.
               '';
             };
             userNameField = mkOption {
               type = types.str;
               default = "";
-              description = ''
+              description = lib.mdDoc ''
                 LDAP field which is used as the username on HedgeDoc.
-                By default <option>useridField</option> is used.
+                By default {option}`useridField` is used.
               '';
             };
             useridField = mkOption {
               type = types.str;
               example = "uid";
-              description = ''
+              description = lib.mdDoc ''
                 LDAP field which is a unique identifier for users on HedgeDoc.
               '';
             };
             tlsca = mkOption {
               type = types.str;
+              default = "/etc/ssl/certs/ca-certificates.crt";
               example = "server-cert.pem,root.pem";
-              description = ''
+              description = lib.mdDoc ''
                 Root CA for LDAP TLS in PEM format.
               '';
             };
           };
         });
         default = null;
-        description = "Configure the LDAP integration.";
+        description = lib.mdDoc "Configure the LDAP integration.";
       };
       saml = mkOption {
         type = types.nullOr (types.submodule {
@@ -870,21 +883,21 @@ in
             idpSsoUrl = mkOption {
               type = types.str;
               example = "https://idp.example.com/sso";
-              description = ''
+              description = lib.mdDoc ''
                 IdP authentication endpoint.
               '';
             };
             idpCert = mkOption {
               type = types.path;
               example = "/path/to/cert.pem";
-              description = ''
+              description = lib.mdDoc ''
                 Path to IdP certificate file in PEM format.
               '';
             };
             issuer = mkOption {
               type = types.str;
               default = "";
-              description = ''
+              description = lib.mdDoc ''
                 Optional identity of the service provider.
                 This defaults to the server URL.
               '';
@@ -892,7 +905,7 @@ in
             identifierFormat = mkOption {
               type = types.str;
               default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
-              description = ''
+              description = lib.mdDoc ''
                 Optional name identifier format.
               '';
             };
@@ -900,7 +913,7 @@ in
               type = types.str;
               default = "";
               example = "memberOf";
-              description = ''
+              description = lib.mdDoc ''
                 Optional attribute name for group list.
               '';
             };
@@ -908,7 +921,7 @@ in
               type = types.listOf types.str;
               default = [];
               example = [ "Temporary-staff" "External-users" ];
-              description = ''
+              description = lib.mdDoc ''
                 Excluded group names.
               '';
             };
@@ -916,15 +929,23 @@ in
               type = types.listOf types.str;
               default = [];
               example = [ "Hedgedoc-Users" ];
-              description = ''
+              description = lib.mdDoc ''
                 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 = ''
+                description = lib.mdDoc ''
                   Attribute map for `id'.
                   Defaults to `NameID' of SAML response.
                 '';
@@ -932,7 +953,7 @@ in
               username = mkOption {
                 type = types.str;
                 default = "";
-                description = ''
+                description = lib.mdDoc ''
                   Attribute map for `username'.
                   Defaults to `NameID' of SAML response.
                 '';
@@ -940,10 +961,10 @@ in
               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.
                 '';
               };
@@ -951,37 +972,45 @@ in
           };
         });
         default = null;
-        description = "Configure the SAML integration.";
+        description = lib.mdDoc "Configure the SAML integration.";
+      };
+    }; in lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+        inherit options;
       };
+      description = lib.mdDoc ''
+        HedgeDoc configuration, see
+        <https://docs.hedgedoc.org/configuration/>
+        for documentation.
+      '';
     };
 
     environmentFile = mkOption {
       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.
       '';
     };
 
@@ -989,16 +1018,17 @@ in
       type = types.package;
       default = pkgs.hedgedoc;
       defaultText = literalExpression "pkgs.hedgedoc";
-      description = ''
+      description = lib.mdDoc ''
         Package that provides HedgeDoc.
       '';
     };
+
   };
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = cfg.configuration.db == {} -> (
-          cfg.configuration.dbURL != "" && cfg.configuration.dbURL != null
+      { assertion = cfg.settings.db == {} -> (
+          cfg.settings.dbURL != "" && cfg.settings.dbURL != null
         );
         message = "Database configuration for HedgeDoc missing."; }
     ];
@@ -1019,10 +1049,12 @@ in
       preStart = ''
         ${pkgs.envsubst}/bin/envsubst \
           -o ${cfg.workDir}/config.json \
-          -i ${prettyJSON cfg.configuration}
+          -i ${prettyJSON cfg.settings}
+        mkdir -p ${cfg.settings.uploadsPath}
       '';
       serviceConfig = {
         WorkingDirectory = cfg.workDir;
+        StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ];
         ExecStart = "${cfg.package}/bin/hedgedoc";
         EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
         Environment = [
diff --git a/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix
index 4f6a34e6d2fe..86716a02649c 100644
--- a/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixos/modules/services/web-apps/hledger-web.nix
@@ -5,14 +5,14 @@ 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;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         Address to listen on.
       '';
     };
@@ -21,7 +21,7 @@ in {
       type = types.port;
       default = 5000;
       example = 80;
-      description = ''
+      description = lib.mdDoc ''
         Port to listen on.
       '';
     };
@@ -30,21 +30,21 @@ in {
       view = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable the view capability.
         '';
       };
       add = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the add capability.
         '';
       };
       manage = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable the manage capability.
         '';
       };
@@ -53,7 +53,7 @@ in {
     stateDir = mkOption {
       type = types.path;
       default = "/var/lib/hledger-web";
-      description = ''
+      description = lib.mdDoc ''
         Path the service has access to. If left as the default value this
         directory will automatically be created before the hledger-web server
         starts, otherwise the sysadmin is responsible for ensuring the
@@ -64,8 +64,8 @@ in {
     journalFiles = mkOption {
       type = types.listOf types.str;
       default = [ ".hledger.journal" ];
-      description = ''
-        Paths to journal files relative to <option>services.hledger-web.stateDir</option>.
+      description = lib.mdDoc ''
+        Paths to journal files relative to {option}`services.hledger-web.stateDir`.
       '';
     };
 
@@ -73,7 +73,7 @@ in {
       type = with types; nullOr str;
       default = null;
       example = "https://example.org";
-      description = ''
+      description = lib.mdDoc ''
         Base URL, when sharing over a network.
       '';
     };
@@ -82,7 +82,7 @@ in {
       type = types.listOf types.str;
       default = [];
       example = [ "--forecast" ];
-      description = ''
+      description = lib.mdDoc ''
         Extra command line arguments to pass to hledger-web.
       '';
     };
diff --git a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
index b9761061aaae..67d235ab4475 100644
--- a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
+++ b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -12,12 +12,12 @@ 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;
       default = poolName;
-      description = ''
+      description = lib.mdDoc ''
          Name of existing PHP-FPM pool that is used to run Icingaweb2.
          If not specified, a pool will automatically created with default values.
       '';
@@ -26,7 +26,7 @@ in {
     libraryPaths = mkOption {
       type = attrsOf package;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Libraries to add to the Icingaweb2 library path.
         The name of the attribute is the name of the library, the value
         is the package to add.
@@ -36,7 +36,7 @@ in {
     virtualHost = mkOption {
       type = nullOr str;
       default = "icingaweb2";
-      description = ''
+      description = lib.mdDoc ''
         Name of the nginx virtualhost to use and setup. If null, no virtualhost is set up.
       '';
     };
@@ -45,15 +45,15 @@ in {
       type = str;
       default = "UTC";
       example = "Europe/Berlin";
-      description = "PHP-compliant timezone specification";
+      description = lib.mdDoc "PHP-compliant timezone specification";
     };
 
     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 {
@@ -64,7 +64,7 @@ in {
           "snow" = icingaweb2Modules.theme-snow;
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Name-package attrset of Icingaweb 2 modules packages to enable.
 
         If you enable modules manually (e.g. via the web ui), they will not be touched.
@@ -84,7 +84,7 @@ in {
           level = "CRITICAL";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         config.ini contents.
         Will automatically be converted to a .ini file.
         If you don't set global.module_path, the module will take care of it.
@@ -108,7 +108,7 @@ in {
           dbname = "icingaweb2";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         resources.ini contents.
         Will automatically be converted to a .ini file.
 
@@ -127,7 +127,7 @@ in {
           resource = "icingaweb_db";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         authentication.ini contents.
         Will automatically be converted to a .ini file.
 
@@ -145,7 +145,7 @@ in {
           resource = "icingaweb_db";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         groups.ini contents.
         Will automatically be converted to a .ini file.
 
@@ -163,7 +163,7 @@ in {
           permissions = "*";
         };
       };
-      description = ''
+      description = lib.mdDoc ''
         roles.ini contents.
         Will automatically be converted to a .ini file.
 
diff --git a/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix b/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
index e9c1d4ffe5ea..9a848870e9da 100644
--- a/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
+++ b/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
@@ -34,50 +34,50 @@ in {
     enable = mkOption {
       type = bool;
       default = true;
-      description = "Whether to enable the icingaweb2 monitoring module.";
+      description = lib.mdDoc "Whether to enable the icingaweb2 monitoring module.";
     };
 
     generalConfig = {
       mutable = mkOption {
         type = bool;
         default = false;
-        description = "Make config.ini of the monitoring module mutable (e.g. via the web interface).";
+        description = lib.mdDoc "Make config.ini of the monitoring module mutable (e.g. via the web interface).";
       };
 
       protectedVars = mkOption {
         type = listOf str;
         default = [ "*pw*" "*pass*" "community" ];
-        description = "List of string patterns for custom variables which should be excluded from user’s view.";
+        description = lib.mdDoc "List of string patterns for custom variables which should be excluded from user’s view.";
       };
     };
 
     mutableBackends = mkOption {
       type = bool;
       default = false;
-      description = "Make backends.ini of the monitoring module mutable (e.g. via the web interface).";
+      description = lib.mdDoc "Make backends.ini of the monitoring module mutable (e.g. via the web interface).";
     };
 
     backends = mkOption {
       default = { icinga = { resource = "icinga_ido"; }; };
-      description = "Monitoring backends to define";
+      description = lib.mdDoc "Monitoring backends to define";
       type = attrsOf (submodule ({ name, ... }: {
         options = {
           name = mkOption {
             visible = false;
             default = name;
             type = str;
-            description = "Name of this backend";
+            description = lib.mdDoc "Name of this backend";
           };
 
           resource = mkOption {
             type = str;
-            description = "Name of the IDO resource";
+            description = lib.mdDoc "Name of the IDO resource";
           };
 
           disabled = mkOption {
             type = bool;
             default = false;
-            description = "Disable this backend";
+            description = lib.mdDoc "Disable this backend";
           };
         };
       }));
@@ -86,62 +86,62 @@ in {
     mutableTransports = mkOption {
       type = bool;
       default = true;
-      description = "Make commandtransports.ini of the monitoring module mutable (e.g. via the web interface).";
+      description = lib.mdDoc "Make commandtransports.ini of the monitoring module mutable (e.g. via the web interface).";
     };
 
     transports = mkOption {
       default = {};
-      description = "Command transports to define";
+      description = lib.mdDoc "Command transports to define";
       type = attrsOf (submodule ({ name, ... }: {
         options = {
           name = mkOption {
             visible = false;
             default = name;
             type = str;
-            description = "Name of this transport";
+            description = lib.mdDoc "Name of this transport";
           };
 
           type = mkOption {
             type = enum [ "api" "local" "remote" ];
             default = "api";
-            description = "Type of  this transport";
+            description = lib.mdDoc "Type of  this transport";
           };
 
           instance = mkOption {
             type = nullOr str;
             default = null;
-            description = "Assign a icinga instance to this transport";
+            description = lib.mdDoc "Assign a icinga instance to this transport";
           };
 
           path = mkOption {
             type = str;
-            description = "Path to the socket for local or remote transports";
+            description = lib.mdDoc "Path to the socket for local or remote transports";
           };
 
           host = mkOption {
             type = str;
-            description = "Host for the api or remote transport";
+            description = lib.mdDoc "Host for the api or remote transport";
           };
 
           port = mkOption {
             type = nullOr str;
             default = null;
-            description = "Port to connect to for the api or remote transport";
+            description = lib.mdDoc "Port to connect to for the api or remote transport";
           };
 
           username = mkOption {
             type = str;
-            description = "Username for the api or remote transport";
+            description = lib.mdDoc "Username for the api or remote transport";
           };
 
           password = mkOption {
             type = str;
-            description = "Password for the api transport";
+            description = lib.mdDoc "Password for the api transport";
           };
 
           resource = mkOption {
             type = str;
-            description = "SSH identity resource for the remote transport";
+            description = lib.mdDoc "SSH identity resource for the remote transport";
           };
         };
       }));
diff --git a/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixos/modules/services/web-apps/ihatemoney/default.nix
index ad314c885ba8..a61aa445f82c 100644
--- a/nixos/modules/services/web-apps/ihatemoney/default.nix
+++ b/nixos/modules/services/web-apps/ihatemoney/default.nix
@@ -47,60 +47,60 @@ let
 in
   {
     options.services.ihatemoney = {
-      enable = mkEnableOption "ihatemoney webapp. Note that this will set uwsgi to emperor mode";
+      enable = mkEnableOption (lib.mdDoc "ihatemoney webapp. Note that this will set uwsgi to emperor mode");
       backend = mkOption {
         type = types.enum [ "sqlite" "postgresql" ];
         default = "sqlite";
-        description = ''
+        description = lib.mdDoc ''
           The database engine to use for ihatemoney.
-          If <literal>postgresql</literal> is selected, then a database called
-          <literal>${db}</literal> will be created. If you disable this option,
+          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 = "The hashed password of the administrator. To obtain it, run <literal>ihatemoney generate_password_hash</literal>";
+        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 = "Additionnal configuration of the UWSGI vassal running ihatemoney. It should notably specify on which interfaces and ports the vassal should listen.";
+        description = lib.mdDoc "Additional 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 = "The display name of the sender of ihatemoney emails";
+          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 = "The email of the sender of ihatemoney emails";
+          description = lib.mdDoc "The email of the sender of ihatemoney emails";
         };
       };
       secureCookie = mkOption {
         type = types.bool;
         default = true;
-        description = "Use secure cookies. Disable this when ihatemoney is served via http instead of https";
+        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";
+      enableDemoProject = mkEnableOption (lib.mdDoc "access to the demo project in ihatemoney");
+      enablePublicProjectCreation = mkEnableOption (lib.mdDoc "permission to create projects in ihatemoney by anyone");
+      enableAdminDashboard = mkEnableOption (lib.mdDoc "ihatemoney admin dashboard");
+      enableCaptcha = mkEnableOption (lib.mdDoc "a simplistic captcha for some forms");
       legalLink = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "The URL to a page explaining legal statements about your service, eg. GDPR-related information.";
+        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 = "Extra configuration appended to ihatemoney's configuration file. It is a python file, so pay attention to indentation.";
+        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 {
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
index 10b30bf1fd1d..61c52ee03dc6 100644
--- a/nixos/modules/services/web-apps/invidious.nix
+++ b/nixos/modules/services/web-apps/invidious.nix
@@ -146,33 +146,33 @@ 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";
-      description = "The Invidious package to use.";
+      defaultText = lib.literalExpression "pkgs.invidious";
+      description = lib.mdDoc "The Invidious package to use.";
     };
 
     settings = lib.mkOption {
       type = settingsFormat.type;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         The settings Invidious should use.
 
-        See <link xlink:href="https://github.com/iv-org/invidious/blob/master/config/config.example.yml">config.example.yml</link> for a list of all possible options.
+        See [config.example.yml](https://github.com/iv-org/invidious/blob/master/config/config.example.yml) for a list of all possible options.
       '';
     };
 
     extraSettingsFile = lib.mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         A file including Invidious settings.
 
-        It gets merged with the setttings specified in <option>services.invidious.settings</option>
-        and can be used to store secrets like <literal>hmac_key</literal> outside of the nix store.
+        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.
       '';
     };
 
@@ -182,7 +182,7 @@ in
     domain = lib.mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The FQDN Invidious is reachable on.
 
         This is used to configure nginx and for building absolute URLs.
@@ -193,12 +193,12 @@ in
       type = types.port;
       # Default from https://docs.invidious.io/Configuration.md
       default = 3000;
-      description = ''
+      description = lib.mdDoc ''
         The port Invidious should listen on.
 
         To allow access from outside,
-        you can use either <option>services.invidious.nginx</option>
-        or add <literal>config.services.invidious.port</literal> to <option>networking.firewall.allowedTCPPorts</option>.
+        you can use either {option}`services.invidious.nginx`
+        or add `config.services.invidious.port` to {option}`networking.firewall.allowedTCPPorts`.
       '';
     };
 
@@ -206,7 +206,7 @@ in
       createLocally = lib.mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create a local database with PostgreSQL.
         '';
       };
@@ -214,10 +214,10 @@ in
       host = lib.mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The database host Invidious should use.
 
-          If <literal>null</literal>, the local unix socket is used. Otherwise
+          If `null`, the local unix socket is used. Otherwise
           TCP is used.
         '';
       };
@@ -226,7 +226,7 @@ in
         type = types.port;
         default = options.services.postgresql.port.default;
         defaultText = lib.literalExpression "options.services.postgresql.port.default";
-        description = ''
+        description = lib.mdDoc ''
           The port of the database Invidious should use.
 
           Defaults to the the default postgresql port.
@@ -237,7 +237,7 @@ in
         type = types.nullOr types.str;
         apply = lib.mapNullable toString;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to file containing the database password.
         '';
       };
@@ -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/nixos/modules/services/web-apps/invoiceplane.nix b/nixos/modules/services/web-apps/invoiceplane.nix
index 095eec36dec3..8be1fd3055d0 100644
--- a/nixos/modules/services/web-apps/invoiceplane.nix
+++ b/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 = ''
-            This directory is used for uploads of attachements and cache.
+          description = lib.mdDoc ''
+            This directory is used for uploads of attachments and cache.
             The directory passed here is automatically created and permissions
             adjusted as required.
           '';
@@ -83,50 +84,53 @@ let
           host = mkOption {
             type = types.str;
             default = "localhost";
-            description = "Database host address.";
+            description = lib.mdDoc "Database host address.";
           };
 
           port = mkOption {
             type = types.port;
             default = 3306;
-            description = "Database host port.";
+            description = lib.mdDoc "Database host port.";
           };
 
           name = mkOption {
             type = types.str;
             default = "invoiceplane";
-            description = "Database name.";
+            description = lib.mdDoc "Database name.";
           };
 
           user = mkOption {
             type = types.str;
             default = "invoiceplane";
-            description = "Database user.";
+            description = lib.mdDoc "Database user.";
           };
 
           passwordFile = mkOption {
             type = types.nullOr types.path;
             default = null;
             example = "/run/keys/invoiceplane-dbpassword";
-            description = ''
+            description = lib.mdDoc ''
               A file containing the password corresponding to
-              <option>database.user</option>.
+              {option}`database.user`.
             '';
           };
 
           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.";
           };
         };
 
         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
@@ -160,8 +164,8 @@ let
             "pm.max_spare_servers" = 4;
             "pm.max_requests" = 500;
           };
-          description = ''
-            Options for the InvoicePlane PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+          description = lib.mdDoc ''
+            Options for the InvoicePlane PHP pool. See the documentation on `php-fpm.conf`
             for details on configuration directives.
           '';
         };
@@ -174,13 +178,33 @@ let
             DISABLE_SETUP=true
             IP_URL=https://invoice.example.com
           '';
-          description = ''
+          description = lib.mdDoc ''
             InvoicePlane configuration. Refer to
-            <link xlink:href="https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example"/>
+            <https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
             for details on supported values.
           '';
         };
 
+        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.";
+          };
+
+        };
+
       };
 
     };
@@ -194,20 +218,20 @@ in
         options.sites = mkOption {
           type = types.attrsOf (types.submodule siteOpts);
           default = {};
-          description = "Specification of one or more WordPress sites to serve";
+          description = lib.mdDoc "Specification of one or more WordPress sites to serve";
         };
 
         options.webserver = mkOption {
           type = types.enum [ "caddy" ];
           default = "caddy";
-          description = ''
+          description = lib.mdDoc ''
             Which webserver to use for virtual host management. Currently only
             caddy is supported.
           '';
         };
       };
       default = {};
-      description = "InvoicePlane configuration.";
+      description = lib.mdDoc "InvoicePlane configuration.";
     };
 
   };
@@ -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;
@@ -236,7 +263,7 @@ in
     };
 
     services.phpfpm = {
-      phpPackage = pkgs.php74;
+      phpPackage = pkgs.php81;
       pools = mapAttrs' (hostName: cfg: (
         nameValuePair "invoiceplane-${hostName}" {
           inherit user;
@@ -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,7 +354,5 @@ in
     };
   })
 
-
   ]);
 }
-
diff --git a/nixos/modules/services/web-apps/isso.nix b/nixos/modules/services/web-apps/isso.nix
index 4c01781a6a2b..1a852ec352f2 100644
--- a/nixos/modules/services/web-apps/isso.nix
+++ b/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/nixos/modules/services/web-apps/jirafeau.nix b/nixos/modules/services/web-apps/jirafeau.nix
index 328c61c8e646..293cbb3af425 100644
--- a/nixos/modules/services/web-apps/jirafeau.nix
+++ b/nixos/modules/services/web-apps/jirafeau.nix
@@ -25,7 +25,7 @@ in
     adminPasswordSha256 = mkOption {
       type = types.str;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         SHA-256 of the desired administration password. Leave blank/unset for no password.
       '';
     };
@@ -33,10 +33,10 @@ in
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/jirafeau/data/";
-      description = "Location of Jirafeau storage directory.";
+      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.
         '';
     };
@@ -58,13 +58,13 @@ in
     hostName = mkOption {
       type = types.str;
       default = "localhost";
-      description = "URL of instance. Must have trailing slash.";
+      description = lib.mdDoc "URL of instance. Must have trailing slash.";
     };
 
     maxUploadSizeMegabytes = mkOption {
       type = types.int;
       default = 0;
-      description = "Maximum upload size of accepted files.";
+      description = lib.mdDoc "Maximum upload size of accepted files.";
     };
 
     maxUploadTimeout = mkOption {
@@ -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.
         '';
     };
 
@@ -89,14 +89,14 @@ in
           serverAliases = [ "wiki.''${config.networking.domain}" ];
         }
       '';
-      description = "Extra configuration for the nginx virtual host of Jirafeau.";
+      description = lib.mdDoc "Extra configuration for the nginx virtual host of Jirafeau.";
     };
 
     package = mkOption {
       type = types.package;
       default = pkgs.jirafeau;
       defaultText = literalExpression "pkgs.jirafeau";
-      description = "Jirafeau package to use";
+      description = lib.mdDoc "Jirafeau package to use";
     };
 
     poolConfig = mkOption {
@@ -109,8 +109,8 @@ in
         "pm.max_spare_servers" = 4;
         "pm.max_requests" = 500;
       };
-      description = ''
-        Options for Jirafeau PHP pool. See documentation on <literal>php-fpm.conf</literal> for
+      description = lib.mdDoc ''
+        Options for Jirafeau PHP pool. See documentation on `php-fpm.conf` for
         details on configuration directives.
       '';
     };
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index be0b5b94fb26..5b0934b2fb76 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/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,12 +46,12 @@ 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;
       example = "meet.example.org";
-      description = ''
+      description = lib.mdDoc ''
         FQDN of the Jitsi Meet instance.
       '';
     };
@@ -65,10 +65,10 @@ in
           defaultLang = "fi";
         }
       '';
-      description = ''
-        Client-side web application settings that override the defaults in <filename>config.js</filename>.
+      description = lib.mdDoc ''
+        Client-side web application settings that override the defaults in {file}`config.js`.
 
-        See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/config.js" /> for default
+        See <https://github.com/jitsi/jitsi-meet/blob/master/config.js> for default
         configuration with comments.
       '';
     };
@@ -76,8 +76,8 @@ in
     extraConfig = mkOption {
       type = lines;
       default = "";
-      description = ''
-        Text to append to <filename>config.js</filename> web application config file.
+      description = lib.mdDoc ''
+        Text to append to {file}`config.js` web application config file.
 
         Can be used to insert JavaScript logic to determine user's region in cascading bridges setup.
       '';
@@ -92,10 +92,10 @@ in
           SHOW_WATERMARK_FOR_GUESTS = false;
         }
       '';
-      description = ''
-        Client-side web-app interface settings that override the defaults in <filename>interface_config.js</filename>.
+      description = lib.mdDoc ''
+        Client-side web-app interface settings that override the defaults in {file}`interface_config.js`.
 
-        See <link xlink:href="https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js" /> for
+        See <https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js> for
         default configuration with comments.
       '';
     };
@@ -104,10 +104,10 @@ in
       enable = mkOption {
         type = bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable Jitsi Videobridge instance and configure it to connect to Prosody.
 
-          Additional configuration is possible with <option>services.jitsi-videobridge</option>.
+          Additional configuration is possible with {option}`services.jitsi-videobridge`.
         '';
       };
 
@@ -115,10 +115,10 @@ in
         type = nullOr str;
         default = null;
         example = "/run/keys/videobridge";
-        description = ''
+        description = lib.mdDoc ''
           File containing password to the Prosody account for videobridge.
 
-          If <literal>null</literal>, a file with password will be generated automatically. Setting
+          If `null`, a file with password will be generated automatically. Setting
           this option is useful if you plan to connect additional videobridges to the XMPP server.
         '';
       };
@@ -127,44 +127,44 @@ in
     jicofo.enable = mkOption {
       type = bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable JiCoFo instance and configure it to connect to Prosody.
 
-        Additional configuration is possible with <option>services.jicofo</option>.
+        Additional configuration is possible with {option}`services.jicofo`.
       '';
     };
 
     jibri.enable = mkOption {
       type = bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable a Jibri instance and configure it to connect to Prosody.
 
-        Additional configuration is possible with <option>services.jibri</option>, and
-        <option>services.jibri.finalizeScript</option> is especially useful.
+        Additional configuration is possible with {option}`services.jibri`, and
+        {option}`services.jibri.finalizeScript` is especially useful.
       '';
     };
 
     nginx.enable = mkOption {
       type = bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable nginx virtual host that will serve the javascript application and act as
         a proxy for the XMPP server. Further nginx configuration can be done by adapting
-        <option>services.nginx.virtualHosts.&lt;hostName&gt;</option>.
+        {option}`services.nginx.virtualHosts.<hostName>`.
         When this is enabled, ACME will be used to retrieve a TLS certificate by default. To disable
-        this, set the <option>services.nginx.virtualHosts.&lt;hostName&gt;.enableACME</option> to
-        <literal>false</literal> and if appropriate do the same for
-        <option>services.nginx.virtualHosts.&lt;hostName&gt;.forceSSL</option>.
+        this, set the {option}`services.nginx.virtualHosts.<hostName>.enableACME` to
+        `false` and if appropriate do the same for
+        {option}`services.nginx.virtualHosts.<hostName>.forceSSL`.
       '';
     };
 
-    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;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to configure Prosody to relay XMPP messages between Jitsi Meet components. Turn this
         off if you want to configure it manually.
       '';
@@ -253,9 +253,21 @@ in
         '';
       };
     };
-    systemd.services.prosody.serviceConfig = mkIf cfg.prosody.enable {
-      EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
-      SupplementaryGroups = [ "jitsi-meet" ];
+    systemd.services.prosody = mkIf cfg.prosody.enable {
+      preStart = let
+        videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret";
+      in ''
+        ${config.services.prosody.package}/bin/prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
+        ${config.services.prosody.package}/bin/prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
+        ${config.services.prosody.package}/bin/prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
+        ${config.services.prosody.package}/bin/prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)"
+        ${config.services.prosody.package}/bin/prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"
+      '';
+      serviceConfig = {
+        EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
+        SupplementaryGroups = [ "jitsi-meet" ];
+      };
+      reloadIfChanged = true;
     };
 
     users.groups.jitsi-meet = {};
@@ -266,14 +278,12 @@ in
     systemd.services.jitsi-meet-init-secrets = {
       wantedBy = [ "multi-user.target" ];
       before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service");
-      path = [ config.services.prosody.package ];
       serviceConfig = {
         Type = "oneshot";
       };
 
       script = let
         secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
-        videobridgeSecret = if cfg.videobridge.passwordFile != null then cfg.videobridge.passwordFile else "/var/lib/jitsi-meet/videobridge-secret";
       in
       ''
         cd /var/lib/jitsi-meet
@@ -291,12 +301,6 @@ in
         chmod 640 secrets-env
       ''
       + optionalString cfg.prosody.enable ''
-        prosodyctl register focus auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jicofo-user-secret)"
-        prosodyctl register jvb auth.${cfg.hostName} "$(cat ${videobridgeSecret})"
-        prosodyctl mod_roster_command subscribe focus.${cfg.hostName} focus@auth.${cfg.hostName}
-        prosodyctl register jibri auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-auth-secret)"
-        prosodyctl register recorder recorder.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"
-
         # generate self-signed certificates
         if [ ! -f /var/lib/jitsi-meet.crt ]; then
           ${getBin pkgs.openssl}/bin/openssl req \
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index 2d817ca19234..d52190a28648 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -20,11 +20,12 @@ let
     mkDefault
     literalExpression
     isAttrs
-    literalDocBook
+    literalMD
     maintainers
     catAttrs
     collect
     splitString
+    hasPrefix
     ;
 
   inherit (builtins)
@@ -98,7 +99,7 @@ in
         type = bool;
         default = false;
         example = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Keycloak identity and access management
           server.
         '';
@@ -109,7 +110,7 @@ in
         default = null;
         example = "/run/keys/ssl_cert";
         apply = assertStringPath "sslCertificate";
-        description = ''
+        description = lib.mdDoc ''
           The path to a PEM formatted certificate to use for TLS/SSL
           connections.
         '';
@@ -120,7 +121,7 @@ in
         default = null;
         example = "/run/keys/ssl_key";
         apply = assertStringPath "sslCertificateKey";
-        description = ''
+        description = lib.mdDoc ''
           The path to a PEM formatted private key to use for TLS/SSL
           connections.
         '';
@@ -129,10 +130,10 @@ in
       plugins = lib.mkOption {
         type = lib.types.listOf lib.types.path;
         default = [ ];
-        description = ''
+        description = lib.mdDoc ''
           Keycloak plugin jar, ear files or derivations containing
           them. Packaged plugins are available through
-          <literal>pkgs.keycloak.plugins</literal>.
+          `pkgs.keycloak.plugins`.
         '';
       };
 
@@ -141,7 +142,7 @@ in
           type = enum [ "mysql" "mariadb" "postgresql" ];
           default = "postgresql";
           example = "mariadb";
-          description = ''
+          description = lib.mdDoc ''
             The type of database Keycloak should connect to.
           '';
         };
@@ -149,7 +150,7 @@ in
         host = mkOption {
           type = str;
           default = "localhost";
-          description = ''
+          description = lib.mdDoc ''
             Hostname of the database to connect to.
           '';
         };
@@ -165,8 +166,8 @@ in
           mkOption {
             type = port;
             default = dbPorts.${cfg.database.type};
-            defaultText = literalDocBook "default port of selected database";
-            description = ''
+            defaultText = literalMD "default port of selected database";
+            description = lib.mdDoc ''
               Port of the database to connect to.
             '';
           };
@@ -175,7 +176,7 @@ in
           type = bool;
           default = cfg.database.host != "localhost";
           defaultText = literalExpression ''config.${opt.database.host} != "localhost"'';
-          description = ''
+          description = lib.mdDoc ''
             Whether the database connection should be secured by SSL /
             TLS.
           '';
@@ -184,13 +185,13 @@ in
         caCert = mkOption {
           type = nullOr path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The SSL / TLS CA certificate that verifies the identity of the
             database server.
 
             Required when PostgreSQL is used and SSL is turned on.
 
-            For MySQL, if left at <literal>null</literal>, the default
+            For MySQL, if left at `null`, the default
             Java keystore is used, which should suffice if the server
             certificate is issued by an official CA.
           '';
@@ -199,7 +200,7 @@ in
         createLocally = mkOption {
           type = bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether a database should be automatically created on the
             local host. Set this to false if you plan on provisioning a
             local database yourself. This has no effect if
@@ -210,14 +211,13 @@ in
         name = mkOption {
           type = str;
           default = "keycloak";
-          description = ''
+          description = lib.mdDoc ''
             Database name to use when connecting to an external or
             manually provisioned database; has no effect when a local
             database is automatically provisioned.
 
-            To use this with a local database, set <xref
-            linkend="opt-services.keycloak.database.createLocally" /> to
-            <literal>false</literal> and create the database and user
+            To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to
+            `false` and create the database and user
             manually.
           '';
         };
@@ -225,14 +225,13 @@ in
         username = mkOption {
           type = str;
           default = "keycloak";
-          description = ''
+          description = lib.mdDoc ''
             Username to use when connecting to an external or manually
             provisioned database; has no effect when a local database is
             automatically provisioned.
 
-            To use this with a local database, set <xref
-            linkend="opt-services.keycloak.database.createLocally" /> to
-            <literal>false</literal> and create the database and user
+            To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to
+            `false` and create the database and user
             manually.
           '';
         };
@@ -241,7 +240,7 @@ in
           type = path;
           example = "/run/keys/db_password";
           apply = assertStringPath "passwordFile";
-          description = ''
+          description = lib.mdDoc ''
             The path to a file containing the database password.
           '';
         };
@@ -251,7 +250,7 @@ in
         type = package;
         default = pkgs.keycloak;
         defaultText = literalExpression "pkgs.keycloak";
-        description = ''
+        description = lib.mdDoc ''
           Keycloak package to use.
         '';
       };
@@ -259,8 +258,8 @@ in
       initialAdminPassword = mkOption {
         type = str;
         default = "changeme";
-        description = ''
-          Initial password set for the <literal>admin</literal>
+        description = lib.mdDoc ''
+          Initial password set for the `admin`
           user. The password is not stored safely and should be changed
           immediately in the admin panel.
         '';
@@ -269,13 +268,13 @@ in
       themes = mkOption {
         type = attrsOf package;
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           Additional theme packages for Keycloak. Each theme is linked into
           subdirectory with a corresponding attribute name.
 
           Theme packages consist of several subdirectories which provide
-          different theme types: for example, <literal>account</literal>,
-          <literal>login</literal> etc. After adding a theme to this option you
+          different theme types: for example, `account`,
+          `login` etc. After adding a theme to this option you
           can select it by its name in Keycloak administration console.
         '';
       };
@@ -289,7 +288,7 @@ in
               type = str;
               default = "0.0.0.0";
               example = "127.0.0.1";
-              description = ''
+              description = lib.mdDoc ''
                 On which address Keycloak should accept new connections.
               '';
             };
@@ -298,7 +297,7 @@ in
               type = port;
               default = 80;
               example = 8080;
-              description = ''
+              description = lib.mdDoc ''
                 On which port Keycloak should listen for new HTTP connections.
               '';
             };
@@ -307,45 +306,42 @@ in
               type = port;
               default = 443;
               example = 8443;
-              description = ''
+              description = lib.mdDoc ''
                 On which port Keycloak should listen for new HTTPS connections.
               '';
             };
 
             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.
+                :::
               '';
             };
 
             hostname = mkOption {
               type = str;
               example = "keycloak.example.com";
-              description = ''
+              description = lib.mdDoc ''
                 The hostname part of the public URL used as base for
                 all frontend requests.
 
-                See <link xlink:href="https://www.keycloak.org/server/hostname" />
+                See <https://www.keycloak.org/server/hostname>
                 for more information about hostname configuration.
               '';
             };
@@ -354,14 +350,14 @@ in
               type = bool;
               default = false;
               example = true;
-              description = ''
+              description = lib.mdDoc ''
                 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.
 
-                See <link xlink:href="https://www.keycloak.org/server/hostname" />
+                See <https://www.keycloak.org/server/hostname>
                 for more information about hostname configuration.
               '';
             };
@@ -370,43 +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.
               '';
             };
           };
@@ -421,22 +395,21 @@ in
           }
         '';
 
-        description = ''
+        description = lib.mdDoc ''
           Configuration options corresponding to parameters set in
-          <filename>conf/keycloak.conf</filename>.
+          {file}`conf/keycloak.conf`.
 
-          Most available options are documented at <link
-          xlink:href="https://www.keycloak.org/server/all-config" />.
+          Most available options are documented at <https://www.keycloak.org/server/all-config>.
 
           Options containing secret data should be set to an attribute
-          set containing the attribute <literal>_secret</literal> - a
+          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
-          <filename>conf/keycloak.conf</filename> file, the
-          <literal>https-key-store-password</literal> key will be set
+          {file}`conf/keycloak.conf` file, the
+          `https-key-store-password` key will be set
           to the contents of the
-          <filename>/run/keys/store_password</filename> file.
+          {file}`/run/keys/store_password` file.
         '';
       };
     };
@@ -509,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 ];
@@ -540,7 +517,8 @@ in
               db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type;
               db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
               db-password._secret = cfg.database.passwordFile;
-              db-url-host = "${cfg.database.host}:${toString cfg.database.port}";
+              db-url-host = cfg.database.host;
+              db-url-port = toString cfg.database.port;
               db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name;
               db-url-properties = prefixUnlessEmpty "?" dbProps;
               db-url = null;
@@ -568,9 +546,15 @@ in
             shopt -s inherit_errexit
 
             create_role="$(mktemp)"
-            trap 'rm -f "$create_role"' ERR EXIT
+            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"'
@@ -592,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
@@ -642,7 +634,7 @@ in
               Group = "keycloak";
               DynamicUser = true;
               RuntimeDirectory = "keycloak";
-              RuntimeDirectoryMode = 0700;
+              RuntimeDirectoryMode = "0700";
               AmbientCapabilities = "CAP_NET_BIND_SERVICE";
             };
             script = ''
@@ -658,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
             '';
           };
 
diff --git a/nixos/modules/services/web-apps/komga.nix b/nixos/modules/services/web-apps/komga.nix
new file mode 100644
index 000000000000..31f475fc7b04
--- /dev/null
+++ b/nixos/modules/services/web-apps/komga.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.komga;
+
+in {
+  options = {
+    services.komga = {
+      enable = mkEnableOption (lib.mdDoc "Komga, a free and open source comics/mangas media server");
+
+      port = mkOption {
+        type = types.port;
+        default = 8080;
+        description = lib.mdDoc ''
+          The port that Komga will listen on.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "komga";
+        description = lib.mdDoc ''
+          User account under which Komga runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "komga";
+        description = lib.mdDoc ''
+          Group under which Komga runs.
+        '';
+      };
+
+      stateDir = mkOption {
+        type = types.str;
+        default = "/var/lib/komga";
+        description = lib.mdDoc ''
+          State and configuration directory Komga will use.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to open the firewall for the port in {option}`services.komga.port`.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+    users.groups = mkIf (cfg.group == "komga") {
+      komga = {};
+    };
+
+    users.users = mkIf (cfg.user == "komga") {
+      komga = {
+        group = cfg.group;
+        home = cfg.stateDir;
+        description = "Komga Daemon user";
+        isSystemUser = true;
+      };
+    };
+
+    systemd.services.komga = {
+      environment = {
+        SERVER_PORT = builtins.toString cfg.port;
+        KOMGA_CONFIGDIR = cfg.stateDir;
+      };
+
+      description = "Komga is a free and open source comics/mangas media server";
+
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+
+        Type = "simple";
+        Restart = "on-failure";
+        ExecStart = "${pkgs.komga}/bin/komga";
+
+        StateDirectory = mkIf (cfg.stateDir == "/var/lib/komga") "komga";
+      };
+
+    };
+  };
+
+  meta.maintainers = with maintainers; [ govanify ];
+}
diff --git a/nixos/modules/services/web-apps/lemmy.md b/nixos/modules/services/web-apps/lemmy.md
index e6599cd843e3..faafe096d138 100644
--- a/nixos/modules/services/web-apps/lemmy.md
+++ b/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/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
index 7cd2357c4556..267584dd0ca7 100644
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -10,28 +10,29 @@ in
   # `pandoc lemmy.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > lemmy.xml`
   meta.doc = ./lemmy.xml;
 
-  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 = "Path to read the jwt secret from.";
-    };
+    enable = mkEnableOption (lib.mdDoc "lemmy a federated alternative to reddit in rust");
 
     ui = {
       port = mkOption {
         type = types.port;
         default = 1234;
-        description = "Port where lemmy-ui should listen for incoming requests.";
+        description = lib.mdDoc "Port where lemmy-ui should listen for incoming requests.";
       };
     };
 
-    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 = { };
-      description = "Lemmy configuration";
+      description = lib.mdDoc "Lemmy configuration";
 
       type = types.submodule {
         freeformType = settingsFormat.type;
@@ -39,43 +40,37 @@ in
         options.hostname = mkOption {
           type = types.str;
           default = null;
-          description = "The domain name of your instance (eg 'lemmy.ml').";
+          description = lib.mdDoc "The domain name of your instance (eg 'lemmy.ml').";
         };
 
         options.port = mkOption {
           type = types.port;
           default = 8536;
-          description = "Port where lemmy should listen for incoming requests.";
+          description = lib.mdDoc "Port where lemmy should listen for incoming requests.";
         };
 
         options.federation = {
-          enabled = mkEnableOption "activitypub federation";
+          enabled = mkEnableOption (lib.mdDoc "activitypub federation");
         };
 
         options.captcha = {
           enabled = mkOption {
             type = types.bool;
             default = true;
-            description = "Enable Captcha.";
+            description = lib.mdDoc "Enable Captcha.";
           };
           difficulty = mkOption {
             type = types.enum [ "easy" "medium" "hard" ];
             default = "medium";
-            description = "The difficultly of the captcha to solve.";
+            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 +97,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 +117,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 +143,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";
       }];
 
@@ -164,22 +164,15 @@ in
 
         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";
         };
       };
 
@@ -210,27 +203,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/nixos/modules/services/web-apps/lemmy.xml b/nixos/modules/services/web-apps/lemmy.xml
index 0be9fb8aefa9..f04316b3c515 100644
--- a/nixos/modules/services/web-apps/lemmy.xml
+++ b/nixos/modules/services/web-apps/lemmy.xml
@@ -8,22 +8,17 @@
     <para>
       the minimum to start lemmy is
     </para>
-    <programlisting language="bash">
+    <programlisting language="nix">
 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
diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index 5ccd742a303b..7093d1de0dac 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -32,48 +32,48 @@ in
   # interface
 
   options.services.limesurvey = {
-    enable = mkEnableOption "Limesurvey web application.";
+    enable = mkEnableOption (lib.mdDoc "Limesurvey web application.");
 
     database = {
       type = mkOption {
         type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ];
         example = "pgsql";
         default = "mysql";
-        description = "Database engine to use.";
+        description = lib.mdDoc "Database engine to use.";
       };
 
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Database host address.";
+        description = lib.mdDoc "Database host address.";
       };
 
       port = mkOption {
-        type = types.int;
+        type = types.port;
         default = if cfg.database.type == "pgsql" then 5442 else 3306;
         defaultText = literalExpression "3306";
-        description = "Database host port.";
+        description = lib.mdDoc "Database host port.";
       };
 
       name = mkOption {
         type = types.str;
         default = "limesurvey";
-        description = "Database name.";
+        description = lib.mdDoc "Database name.";
       };
 
       user = mkOption {
         type = types.str;
         default = "limesurvey";
-        description = "Database user.";
+        description = lib.mdDoc "Database user.";
       };
 
       passwordFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/run/keys/limesurvey-dbpassword";
-        description = ''
+        description = lib.mdDoc ''
           A file containing the password corresponding to
-          <option>database.user</option>.
+          {option}`database.user`.
         '';
       };
 
@@ -85,14 +85,14 @@ in
           else null
         ;
         defaultText = literalExpression "/run/mysqld/mysqld.sock";
-        description = "Path to the unix socket file to use for authentication.";
+        description = lib.mdDoc "Path to the unix socket file to use for authentication.";
       };
 
       createLocally = mkOption {
         type = types.bool;
         default = cfg.database.type == "mysql";
         defaultText = literalExpression "true";
-        description = ''
+        description = lib.mdDoc ''
           Create the database and database user locally.
           This currently only applies if database type "mysql" is selected.
         '';
@@ -109,9 +109,9 @@ in
           enableACME = true;
         }
       '';
-      description = ''
-        Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
-        See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+      description = lib.mdDoc ''
+        Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
+        See [](#opt-services.httpd.virtualHosts) for further information.
       '';
     };
 
@@ -125,8 +125,8 @@ in
         "pm.max_spare_servers" = 4;
         "pm.max_requests" = 500;
       };
-      description = ''
-        Options for the LimeSurvey PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+      description = lib.mdDoc ''
+        Options for the LimeSurvey PHP pool. See the documentation on `php-fpm.conf`
         for details on configuration directives.
       '';
     };
@@ -134,9 +134,9 @@ in
     config = mkOption {
       type = configType;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         LimeSurvey configuration. Refer to
-        <link xlink:href="https://manual.limesurvey.org/Optional_settings"/>
+        <https://manual.limesurvey.org/Optional_settings>
         for details on supported values.
       '';
     };
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index fbfcc33b2dce..b6e2309555f2 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/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" ];
@@ -92,63 +94,64 @@ let
       ] else []
     ) env))));
 
-  mastodonEnv = pkgs.writeShellScriptBin "mastodon-env" ''
+  mastodonTootctl = pkgs.writeShellScriptBin "mastodon-tootctl" ''
+    #! ${pkgs.runtimeShell}
     set -a
     export RAILS_ROOT="${cfg.package}"
     source "${envFile}"
     source /var/lib/mastodon/.secrets_env
-    eval -- "\$@"
+
+    sudo=exec
+    if [[ "$USER" != ${cfg.user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env'
+    fi
+    $sudo ${cfg.package}/bin/tootctl "$@"
   '';
 
 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:
 
-          <code>/ -> $(nix-instantiate --eval '&lt;nixpkgs&gt;' -A mastodon.outPath)/public</code>
+          `/ -> $(nix-instantiate --eval '<nixpkgs>' -A mastodon.outPath)/public`
 
-          <code>/ -> 127.0.0.1:{{ webPort }} </code>(If there was no file in the directory above.)
+          `/ -> 127.0.0.1:{{ webPort }} `(If there was no file in the directory above.)
 
-          <code>/system/ -> /var/lib/mastodon/public-system/</code>
+          `/system/ -> /var/lib/mastodon/public-system/`
 
-          <code>/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}</code>
+          `/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
-          <code>https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf</code>.
+          `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 <code>mastodon-env</code> will be added to
-          the user's package set. To run a command from
-          <package>mastodon</package> such as <code>tootctl</code>
-          with the environment configured by this module use
-          <code>mastodon-env</code>, as in:
-
-          <code>mastodon-env tootctl accounts create newuser --email newuser@example.com</code>
+          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";
       };
 
       group = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Group under which mastodon runs.
         '';
         type = lib.types.str;
@@ -156,12 +159,12 @@ in {
       };
 
       streamingPort = lib.mkOption {
-        description = "TCP port used by the mastodon-streaming service.";
+        description = lib.mdDoc "TCP port used by the mastodon-streaming service.";
         type = lib.types.port;
         default = 55000;
       };
       streamingProcesses = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Processes used by the mastodon-streaming service.
           Defaults to the number of CPU cores minus one.
         '';
@@ -170,41 +173,41 @@ in {
       };
 
       webPort = lib.mkOption {
-        description = "TCP port used by the mastodon-web service.";
+        description = lib.mdDoc "TCP port used by the mastodon-web service.";
         type = lib.types.port;
         default = 55001;
       };
       webProcesses = lib.mkOption {
-        description = "Processes used by the mastodon-web service.";
+        description = lib.mdDoc "Processes used by the mastodon-web service.";
         type = lib.types.int;
         default = 2;
       };
       webThreads = lib.mkOption {
-        description = "Threads per process used by the mastodon-web service.";
+        description = lib.mdDoc "Threads per process used by the mastodon-web service.";
         type = lib.types.int;
         default = 5;
       };
 
       sidekiqPort = lib.mkOption {
-        description = "TCP port used by the mastodon-sidekiq service.";
+        description = lib.mdDoc "TCP port used by the mastodon-sidekiq service.";
         type = lib.types.port;
         default = 55002;
       };
       sidekiqThreads = lib.mkOption {
-        description = "Worker threads used by the mastodon-sidekiq service.";
+        description = lib.mdDoc "Worker threads used by the mastodon-sidekiq service.";
         type = lib.types.int;
         default = 25;
       };
 
       vapidPublicKeyFile = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Path to file containing the public key used for Web Push
           Voluntary Application Server Identification.  A new keypair can
           be generated by running:
 
-          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake webpush:generate_keys</code>
+          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys`
 
-          If <option>mastodon.vapidPrivateKeyFile</option>does not
+          If {option}`mastodon.vapidPrivateKeyFile`does not
           exist, it and this file will be created with a new keypair.
         '';
         default = "/var/lib/mastodon/secrets/vapid-public-key";
@@ -212,17 +215,17 @@ in {
       };
 
       localDomain = lib.mkOption {
-        description = "The domain serving your Mastodon instance.";
+        description = lib.mdDoc "The domain serving your Mastodon instance.";
         example = "social.example.org";
         type = lib.types.str;
       };
 
       secretKeyBaseFile = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Path to file containing the secret key base.
           A new secret key base can be generated by running:
 
-          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake secret</code>
+          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret`
 
           If this file does not exist, it will be created with a new secret key base.
         '';
@@ -231,11 +234,11 @@ in {
       };
 
       otpSecretFile = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Path to file containing the OTP secret.
           A new OTP secret can be generated by running:
 
-          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake secret</code>
+          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret`
 
           If this file does not exist, it will be created with a new OTP secret.
         '';
@@ -244,12 +247,12 @@ in {
       };
 
       vapidPrivateKeyFile = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Path to file containing the private key used for Web Push
           Voluntary Application Server Identification.  A new keypair can
           be generated by running:
 
-          <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake webpush:generate_keys</code>
+          `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys`
 
           If this file does not exist, it will be created with a new
           private key.
@@ -259,7 +262,7 @@ in {
       };
 
       trustedProxy = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           You need to set it to the IP from which your reverse proxy sends requests to Mastodon's web process,
           otherwise Mastodon will record the reverse proxy's own IP as the IP of all requests, which would be
           bad because IP addresses are used for important rate limits and security functions.
@@ -269,7 +272,7 @@ in {
       };
 
       enableUnixSocket = lib.mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Instead of binding to an IP address like 127.0.0.1, you may bind to a Unix socket. This variable
           is process-specific, e.g. you need different values for every process, and it works for both web (Puma)
           processes and streaming API (Node.js) processes.
@@ -280,27 +283,27 @@ in {
 
       redis = {
         createLocally = lib.mkOption {
-          description = "Configure local Redis server for Mastodon.";
+          description = lib.mdDoc "Configure local Redis server for Mastodon.";
           type = lib.types.bool;
           default = true;
         };
 
         host = lib.mkOption {
-          description = "Redis host.";
+          description = lib.mdDoc "Redis host.";
           type = lib.types.str;
           default = "127.0.0.1";
         };
 
         port = lib.mkOption {
-          description = "Redis port.";
+          description = lib.mdDoc "Redis port.";
           type = lib.types.port;
-          default = 6379;
+          default = 31637;
         };
       };
 
       database = {
         createLocally = lib.mkOption {
-          description = "Configure local PostgreSQL database server for Mastodon.";
+          description = lib.mdDoc "Configure local PostgreSQL database server for Mastodon.";
           type = lib.types.bool;
           default = true;
         };
@@ -309,86 +312,93 @@ in {
           type = lib.types.str;
           default = "/run/postgresql";
           example = "192.168.23.42";
-          description = "Database host address or unix socket.";
+          description = lib.mdDoc "Database host address or unix socket.";
         };
 
         port = lib.mkOption {
-          type = lib.types.int;
-          default = 5432;
-          description = "Database host port.";
+          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.";
         };
 
         name = lib.mkOption {
           type = lib.types.str;
           default = "mastodon";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = lib.mkOption {
           type = lib.types.str;
           default = "mastodon";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         passwordFile = lib.mkOption {
           type = lib.types.nullOr lib.types.path;
-          default = "/var/lib/mastodon/secrets/db-password";
-          example = "/run/keys/mastodon-db-password";
-          description = ''
+          default = null;
+          example = "/var/lib/mastodon/secrets/db-password";
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
       };
 
       smtp = {
         createLocally = lib.mkOption {
-          description = "Configure local Postfix SMTP server for Mastodon.";
+          description = lib.mdDoc "Configure local Postfix SMTP server for Mastodon.";
           type = lib.types.bool;
           default = true;
         };
 
         authenticate = lib.mkOption {
-          description = "Authenticate with the SMTP server using username and password.";
+          description = lib.mdDoc "Authenticate with the SMTP server using username and password.";
           type = lib.types.bool;
           default = false;
         };
 
         host = lib.mkOption {
-          description = "SMTP host used when sending emails to users.";
+          description = lib.mdDoc "SMTP host used when sending emails to users.";
           type = lib.types.str;
           default = "127.0.0.1";
         };
 
         port = lib.mkOption {
-          description = "SMTP port used when sending emails to users.";
+          description = lib.mdDoc "SMTP port used when sending emails to users.";
           type = lib.types.port;
           default = 25;
         };
 
         fromAddress = lib.mkOption {
-          description = ''"From" address used when sending Emails to users.'';
+          description = lib.mdDoc ''"From" address used when sending Emails to users.'';
           type = lib.types.str;
         };
 
         user = lib.mkOption {
-          description = "SMTP login name.";
-          type = lib.types.str;
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "mastodon@example.com";
+          description = lib.mdDoc "SMTP login name.";
         };
 
         passwordFile = lib.mkOption {
-          description = ''
+          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;
         };
       };
 
       elasticsearch = {
         host = lib.mkOption {
-          description = ''
+          description = lib.mdDoc ''
             Elasticsearch host.
             If it is not null, Elasticsearch full text search will be enabled.
           '';
@@ -397,7 +407,7 @@ in {
         };
 
         port = lib.mkOption {
-          description = "Elasticsearch port.";
+          description = lib.mdDoc "Elasticsearch port.";
           type = lib.types.port;
           default = 9200;
         };
@@ -407,13 +417,13 @@ in {
         type = lib.types.package;
         default = pkgs.mastodon;
         defaultText = lib.literalExpression "pkgs.mastodon";
-        description = "Mastodon package to use.";
+        description = lib.mdDoc "Mastodon package to use.";
       };
 
       extraConfig = lib.mkOption {
         type = lib.types.attrs;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Extra environment variables to pass to all mastodon services.
         '';
       };
@@ -421,10 +431,43 @@ in {
       automaticMigrations = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           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.
+          '';
+        };
+      };
     };
   };
 
@@ -432,10 +475,37 @@ in {
     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.
+        '';
       }
     ];
 
+    environment.systemPackages = [ mastodonTootctl ];
+
     systemd.services.mastodon-init-dirs = {
       script = ''
         umask 077
@@ -460,10 +530,11 @@ 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;
@@ -475,11 +546,19 @@ in {
       } // 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 +569,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" ];
         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" ]);
-      description = "Mastodon streaming";
+      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 = [ "multi-user.target" ];
+      description = "Mastodon streaming";
       environment = env // (if cfg.enableUnixSocket
         then { SOCKET = "/run/mastodon-streaming/streaming.socket"; }
         else { PORT = toString(cfg.streamingPort); }
@@ -518,7 +608,7 @@ in {
         ExecStart = "${cfg.package}/run-streaming.sh";
         Restart = "always";
         RestartSec = 20;
-        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ];
         WorkingDirectory = cfg.package;
         # Runtime directory and mode
         RuntimeDirectory = "mastodon-streaming";
@@ -529,11 +619,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" ]);
-      description = "Mastodon web";
+      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 = [ "multi-user.target" ];
+      description = "Mastodon web";
       environment = env // (if cfg.enableUnixSocket
         then { SOCKET = "/run/mastodon-web/web.socket"; }
         else { PORT = toString(cfg.webPort); }
@@ -542,7 +635,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" ];
         WorkingDirectory = cfg.package;
         # Runtime directory and mode
         RuntimeDirectory = "mastodon-web";
@@ -554,11 +647,14 @@ in {
     };
 
     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";
+      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 = [ "multi-user.target" ];
+      description = "Mastodon sidekiq";
       environment = env // {
         PORT = toString(cfg.sidekiqPort);
         DB_POOL = toString cfg.sidekiqThreads;
@@ -567,7 +663,7 @@ in {
         ExecStart = "${cfg.package}/bin/sidekiq -c ${toString cfg.sidekiqThreads} -r ${cfg.package}";
         Restart = "always";
         RestartSec = 20;
-        EnvironmentFile = "/var/lib/mastodon/.secrets_env";
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ];
         WorkingDirectory = cfg.package;
         # System Call Filtering
         SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
@@ -575,13 +671,30 @@ in {
       path = with pkgs; [ file imagemagick ffmpeg ];
     };
 
+    systemd.services.mastodon-media-auto-remove = lib.mkIf cfg.mediaAutoRemove.enable {
+      description = "Mastodon media auto remove";
+      environment = env;
+      serviceConfig = {
+        Type = "oneshot";
+        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ];
+      } // cfgService;
+      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 {
       enable = true;
       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/";
 
@@ -605,8 +718,10 @@ in {
       enable = true;
       hostname = lib.mkDefault "${cfg.localDomain}";
     };
-    services.redis = lib.mkIf (cfg.redis.createLocally && cfg.redis.host == "127.0.0.1") {
+    services.redis.servers.mastodon = lib.mkIf (cfg.redis.createLocally && cfg.redis.host == "127.0.0.1") {
       enable = true;
+      port = cfg.redis.port;
+      bind = "127.0.0.1";
     };
     services.postgresql = lib.mkIf databaseActuallyCreateLocally {
       enable = true;
@@ -627,7 +742,7 @@ 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;
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index c6d4ed6d39de..0435d21ce8a2 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/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" ])
@@ -32,7 +30,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable Matomo web analytics with php-fpm backend.
           Either the nginx option or the webServerUser option is mandatory.
         '';
@@ -40,7 +38,7 @@ in {
 
       package = mkOption {
         type = types.package;
-        description = ''
+        description = lib.mdDoc ''
           Matomo package for the service to use.
           This can be used to point to newer releases from nixos-unstable,
           as they don't get backported if they are not security-relevant.
@@ -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.
@@ -64,27 +62,25 @@ in {
       periodicArchiveProcessing = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Enable periodic archive processing, which generates aggregated reports from the visits.
 
           This means that you can safely disable browser triggers for Matomo archiving,
           and safely enable to delete old visitor logs.
           Before deleting visitor logs,
-          make sure though that you run <literal>systemctl start matomo-archive-processing.service</literal>
+          make sure though that you run `systemctl start matomo-archive-processing.service`
           at least once without errors if you have already collected data before.
         '';
       };
 
       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 = ''
+        description = lib.mdDoc ''
           URL of the host, without https prefix. You may want to change it if you
           run Matomo on a different URL than matomo.yourdomain.
         '';
@@ -112,12 +108,12 @@ in {
             enableACME = false;
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
             With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo.
             Either this option or the webServerUser option is mandatory.
             Set this to {} to just enable the virtualHost if you don't need any customization.
-            If enabled, then by default, the <option>serverName</option> is
-            <literal>''${user}.''${config.networking.hostName}.''${config.networking.domain}</literal>,
+            If enabled, then by default, the {option}`serverName` is
+            `''${user}.''${config.networking.hostName}.''${config.networking.domain}`,
             SSL is active, and certificates are acquired via ACME.
             If this is set to null (the default), no nginx virtualHost will be configured.
         '';
@@ -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
diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix
index 2901f307dc5a..56a53198b3fb 100644
--- a/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixos/modules/services/web-apps/mattermost.nix
@@ -101,25 +101,25 @@ 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";
-        description = "Mattermost derivation to use.";
+        defaultText = lib.literalExpression "pkgs.mattermost";
+        description = lib.mdDoc "Mattermost derivation to use.";
       };
 
       statePath = mkOption {
         type = types.str;
         default = "/var/lib/mattermost";
-        description = "Mattermost working directory";
+        description = lib.mdDoc "Mattermost working directory";
       };
 
       siteUrl = mkOption {
         type = types.str;
         example = "https://chat.example.com";
-        description = ''
+        description = lib.mdDoc ''
           URL this Mattermost instance is reachable under, without trailing slash.
         '';
       };
@@ -127,14 +127,14 @@ in
       siteName = mkOption {
         type = types.str;
         default = "Mattermost";
-        description = "Name of this Mattermost site.";
+        description = lib.mdDoc "Name of this Mattermost site.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = ":8065";
         example = "[::1]:8065";
-        description = ''
+        description = lib.mdDoc ''
           Address and port this Mattermost instance listens to.
         '';
       };
@@ -142,7 +142,7 @@ in
       mutableConfig = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether the Mattermost config.json is writeable by Mattermost.
 
           Most of the settings can be edited in the system console of
@@ -159,7 +159,7 @@ in
       preferNixConfig = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           If both mutableConfig and this option are set, the Nix configuration
           will take precedence over any settings configured in the server
           console.
@@ -169,8 +169,8 @@ in
       extraConfig = mkOption {
         type = types.attrs;
         default = { };
-        description = ''
-          Addtional configuration options as Nix attribute set in config.json schema.
+        description = lib.mdDoc ''
+          Additional configuration options as Nix attribute set in config.json schema.
         '';
       };
 
@@ -178,7 +178,7 @@ in
         type = types.listOf (types.oneOf [types.path types.package]);
         default = [];
         example = "[ ./com.github.moussetc.mattermost.plugin.giphy-2.0.0.tar.gz ]";
-        description = ''
+        description = lib.mdDoc ''
           Plugins to add to the configuration. Overrides any installed if non-null.
           This is a list of paths to .tar.gz files or derivations evaluating to
           .tar.gz files.
@@ -188,7 +188,7 @@ in
       localDatabaseCreate = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Create a local PostgreSQL database for Mattermost automatically.
         '';
       };
@@ -196,7 +196,7 @@ in
       localDatabaseName = mkOption {
         type = types.str;
         default = "mattermost";
-        description = ''
+        description = lib.mdDoc ''
           Local Mattermost database name.
         '';
       };
@@ -204,7 +204,7 @@ in
       localDatabaseUser = mkOption {
         type = types.str;
         default = "mattermost";
-        description = ''
+        description = lib.mdDoc ''
           Local Mattermost database username.
         '';
       };
@@ -212,7 +212,7 @@ in
       localDatabasePassword = mkOption {
         type = types.str;
         default = "mmpgsecret";
-        description = ''
+        description = lib.mdDoc ''
           Password for local Mattermost database user.
         '';
       };
@@ -220,7 +220,7 @@ in
       user = mkOption {
         type = types.str;
         default = "mattermost";
-        description = ''
+        description = lib.mdDoc ''
           User which runs the Mattermost service.
         '';
       };
@@ -228,24 +228,24 @@ in
       group = mkOption {
         type = types.str;
         default = "mattermost";
-        description = ''
+        description = lib.mdDoc ''
           Group which runs the Mattermost service.
         '';
       };
 
       matterircd = {
-        enable = mkEnableOption "Mattermost IRC bridge";
+        enable = mkEnableOption (lib.mdDoc "Mattermost IRC bridge");
         package = mkOption {
           type = types.package;
           default = pkgs.matterircd;
-          defaultText = "pkgs.matterircd";
-          description = "matterircd derivation to use.";
+          defaultText = lib.literalExpression "pkgs.matterircd";
+          description = lib.mdDoc "matterircd derivation to use.";
         };
         parameters = mkOption {
           type = types.listOf types.str;
           default = [ ];
           example = [ "-mmserver chat.example.com" "-bind [::]:6667" ];
-          description = ''
+          description = lib.mdDoc ''
             Set commandline parameters to pass to matterircd. See
             https://github.com/42wim/matterircd#usage for more information.
           '';
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index 977b6f60b230..07f296748629 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -35,7 +35,7 @@ let
   };
 
   mediawikiScripts = pkgs.runCommand "mediawiki-scripts" {
-    buildInputs = [ pkgs.makeWrapper ];
+    nativeBuildInputs = [ pkgs.makeWrapper ];
     preferLocalBuild = true;
   } ''
     mkdir -p $out/bin
@@ -129,7 +129,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,26 +171,26 @@ in
   options = {
     services.mediawiki = {
 
-      enable = mkEnableOption "MediaWiki";
+      enable = mkEnableOption (lib.mdDoc "MediaWiki");
 
       package = mkOption {
         type = types.package;
         default = pkgs.mediawiki;
         defaultText = literalExpression "pkgs.mediawiki";
-        description = "Which MediaWiki package to use.";
+        description = lib.mdDoc "Which MediaWiki package to use.";
       };
 
       name = mkOption {
         type = types.str;
         default = "MediaWiki";
         example = "Foobar Wiki";
-        description = "Name of the wiki.";
+        description = lib.mdDoc "Name of the wiki.";
       };
 
       uploadsDir = mkOption {
         type = types.nullOr types.path;
         default = "${stateDir}/uploads";
-        description = ''
+        description = lib.mdDoc ''
           This directory is used for uploads of pictures. The directory passed here is automatically
           created and permissions adjusted as required.
         '';
@@ -198,15 +198,15 @@ in
 
       passwordFile = mkOption {
         type = types.path;
-        description = "A file containing the initial password for the admin user.";
+        description = lib.mdDoc "A file containing the initial password for the admin user.";
         example = "/run/keys/mediawiki-password";
       };
 
       skins = mkOption {
         default = {};
         type = types.attrsOf types.path;
-        description = ''
-          Attribute set of paths whose content is copied to the <filename>skins</filename>
+        description = lib.mdDoc ''
+          Attribute set of paths whose content is copied to the {file}`skins`
           subdirectory of the MediaWiki installation in addition to the default skins.
         '';
       };
@@ -214,11 +214,11 @@ in
       extensions = mkOption {
         default = {};
         type = types.attrsOf (types.nullOr types.path);
-        description = ''
-          Attribute set of paths whose content is copied to the <filename>extensions</filename>
+        description = lib.mdDoc ''
+          Attribute set of paths whose content is copied to the {file}`extensions`
           subdirectory of the MediaWiki installation and enabled in configuration.
 
-          Use <literal>null</literal> instead of path to enable extensions that are part of MediaWiki.
+          Use `null` instead of path to enable extensions that are part of MediaWiki.
         '';
         example = literalExpression ''
           {
@@ -235,52 +235,52 @@ in
         type = mkOption {
           type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ];
           default = "mysql";
-          description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers.";
+          description = lib.mdDoc "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers.";
         };
 
         host = mkOption {
           type = types.str;
           default = "localhost";
-          description = "Database host address.";
+          description = lib.mdDoc "Database host address.";
         };
 
         port = mkOption {
           type = types.port;
           default = 3306;
-          description = "Database host port.";
+          description = lib.mdDoc "Database host port.";
         };
 
         name = mkOption {
           type = types.str;
           default = "mediawiki";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = mkOption {
           type = types.str;
           default = "mediawiki";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/mediawiki-dbpassword";
-          description = ''
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
 
         tablePrefix = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             If you only have access to a single database and wish to install more than
             one version of MediaWiki, or have other applications that also use the
             database, you can give the table names a unique prefix to stop any naming
             conflicts or confusion.
-            See <link xlink:href='https://www.mediawiki.org/wiki/Manual:$wgDBprefix'/>.
+            See <https://www.mediawiki.org/wiki/Manual:$wgDBprefix>.
           '';
         };
 
@@ -288,14 +288,14 @@ in
           type = types.nullOr types.path;
           default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null;
           defaultText = literalExpression "/run/mysqld/mysqld.sock";
-          description = "Path to the unix socket file to use for authentication.";
+          description = lib.mdDoc "Path to the unix socket file to use for authentication.";
         };
 
         createLocally = mkOption {
           type = types.bool;
           default = cfg.database.type == "mysql";
           defaultText = literalExpression "true";
-          description = ''
+          description = lib.mdDoc ''
             Create the database and database user locally.
             This currently only applies if database type "mysql" is selected.
           '';
@@ -312,9 +312,9 @@ in
             enableACME = true;
           }
         '';
-        description = ''
-          Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
-          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+        description = lib.mdDoc ''
+          Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
+          See [](#opt-services.httpd.virtualHosts) for further information.
         '';
       };
 
@@ -328,18 +328,18 @@ in
           "pm.max_spare_servers" = 4;
           "pm.max_requests" = 500;
         };
-        description = ''
-          Options for the MediaWiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        description = lib.mdDoc ''
+          Options for the MediaWiki PHP pool. See the documentation on `php-fpm.conf`
           for details on configuration directives.
         '';
       };
 
       extraConfig = mkOption {
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Any additional text to be appended to MediaWiki's
           LocalSettings.php configuration file. For configuration
-          settings, see <link xlink:href="https://www.mediawiki.org/wiki/Manual:Configuration_settings"/>.
+          settings, see <https://www.mediawiki.org/wiki/Manual:Configuration_settings>.
         '';
         default = "";
         example = ''
@@ -449,6 +449,7 @@ in
           --dbuser ${cfg.database.user} \
           ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \
           --passfile ${cfg.passwordFile} \
+          --dbtype ${cfg.database.type} \
           ${cfg.name} \
           admin
 
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 641c9be85d8c..7cc8ce10ffe0 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/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;
@@ -29,9 +36,9 @@ in
             LISTEN_ADDR = "localhost:8080";
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Configuration for Miniflux, refer to
-          <link xlink:href="https://miniflux.app/docs/configuration.html"/>
+          <https://miniflux.app/docs/configuration.html>
           for documentation on the supported values.
 
           Correct configuration for the database is already provided.
@@ -41,7 +48,7 @@ in
 
       adminCredentialsFile = mkOption  {
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           File containing the ADMIN_USERNAME and
           ADMIN_PASSWORD (length >= 6) in the format of
           an EnvironmentFile=, as described by systemd.exec(5).
@@ -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/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index 19f3e754691e..5f8d9c5b15f4 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -56,25 +56,27 @@ let
   mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
 
-  phpExt = pkgs.php74.withExtensions
-        ({ enabled, all }: with all; [ iconv mbstring curl openssl tokenizer xmlrpc soap ctype zip gd simplexml dom  intl json sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter opcache ]);
+  phpExt = pkgs.php80.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;
       default = pkgs.moodle;
       defaultText = literalExpression "pkgs.moodle";
-      description = "The Moodle package to use.";
+      description = lib.mdDoc "The Moodle package to use.";
     };
 
     initialPassword = mkOption {
       type = types.str;
       example = "correcthorsebatterystaple";
-      description = ''
+      description = lib.mdDoc ''
         Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist.
         The password specified here is world-readable in the Nix store, so it should be changed promptly.
       '';
@@ -84,18 +86,18 @@ in
       type = mkOption {
         type = types.enum [ "mysql" "pgsql" ];
         default = "mysql";
-        description = "Database engine to use.";
+        description = lib.mdDoc "Database engine to use.";
       };
 
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Database host address.";
+        description = lib.mdDoc "Database host address.";
       };
 
       port = mkOption {
-        type = types.int;
-        description = "Database host port.";
+        type = types.port;
+        description = lib.mdDoc "Database host port.";
         default = {
           mysql = 3306;
           pgsql = 5432;
@@ -106,22 +108,22 @@ in
       name = mkOption {
         type = types.str;
         default = "moodle";
-        description = "Database name.";
+        description = lib.mdDoc "Database name.";
       };
 
       user = mkOption {
         type = types.str;
         default = "moodle";
-        description = "Database user.";
+        description = lib.mdDoc "Database user.";
       };
 
       passwordFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/run/keys/moodle-dbpassword";
-        description = ''
+        description = lib.mdDoc ''
           A file containing the password corresponding to
-          <option>database.user</option>.
+          {option}`database.user`.
         '';
       };
 
@@ -132,13 +134,13 @@ in
           else if pgsqlLocal then "/run/postgresql"
           else null;
         defaultText = literalExpression "/run/mysqld/mysqld.sock";
-        description = "Path to the unix socket file to use for authentication.";
+        description = lib.mdDoc "Path to the unix socket file to use for authentication.";
       };
 
       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.";
       };
     };
 
@@ -152,9 +154,9 @@ in
           enableACME = true;
         }
       '';
-      description = ''
-        Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
-        See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+      description = lib.mdDoc ''
+        Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
+        See [](#opt-services.httpd.virtualHosts) for further information.
       '';
     };
 
@@ -168,8 +170,8 @@ in
         "pm.max_spare_servers" = 4;
         "pm.max_requests" = 500;
       };
-      description = ''
-        Options for the Moodle PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+      description = lib.mdDoc ''
+        Options for the Moodle PHP pool. See the documentation on `php-fpm.conf`
         for details on configuration directives.
       '';
     };
@@ -177,10 +179,10 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Any additional text to be appended to the config.php
         configuration file. This is a PHP script. For configuration
-        details, see <link xlink:href="https://docs.moodle.org/37/en/Configuration_file"/>.
+        details, see <https://docs.moodle.org/37/en/Configuration_file>.
       '';
       example = ''
         $CFG->disableupdatenotifications = true;
@@ -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/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
index a7d8bede74b4..e028f16004ef 100644
--- a/nixos/modules/services/web-apps/netbox.nix
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -9,6 +9,10 @@ let
     name = "configuration.py";
     text = ''
       STATIC_ROOT = '${staticDir}'
+      MEDIA_ROOT = '${cfg.dataDir}/media'
+      REPORTS_ROOT = '${cfg.dataDir}/reports'
+      SCRIPTS_ROOT = '${cfg.dataDir}/scripts'
+
       ALLOWED_HOSTS = ['*']
       DATABASE = {
         'NAME': 'netbox',
@@ -42,11 +46,10 @@ let
     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}
@@ -59,18 +62,18 @@ in {
     enable = mkOption {
       type = lib.types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable Netbox.
 
-        This module requires a reverse proxy that serves <literal>/static</literal> separately.
-        See this <link xlink:href="https://github.com/netbox-community/netbox/blob/develop/contrib/nginx.conf/">example</link> on how to configure this.
+        This module requires a reverse proxy that serves `/static` separately.
+        See this [example](https://github.com/netbox-community/netbox/blob/develop/contrib/nginx.conf/) on how to configure this.
       '';
     };
 
     listenAddress = mkOption {
       type = types.str;
       default = "[::1]";
-      description = ''
+      description = lib.mdDoc ''
         Address the server will listen on.
       '';
     };
@@ -78,7 +81,7 @@ in {
     port = mkOption {
       type = types.port;
       default = 8001;
-      description = ''
+      description = lib.mdDoc ''
         Port the server will listen on.
       '';
     };
@@ -89,7 +92,7 @@ in {
       defaultText = literalExpression ''
         python3Packages: with python3Packages; [];
       '';
-      description = ''
+      description = lib.mdDoc ''
         List of plugin packages to install.
       '';
     };
@@ -97,14 +100,14 @@ in {
     dataDir = mkOption {
       type = types.str;
       default = "/var/lib/netbox";
-      description = ''
+      description = lib.mdDoc ''
         Storage path of netbox.
       '';
     };
 
     secretKeyFile = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to a file containing the secret key.
       '';
     };
@@ -112,33 +115,35 @@ in {
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
-        Additional lines of configuration appended to the <literal>configuration.py</literal>.
-        See the <link xlink:href="https://netbox.readthedocs.io/en/stable/configuration/optional-settings/">documentation</link> for more possible options.
+      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.
       '';
     };
 
     enableLdap = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable LDAP-Authentication for Netbox.
 
-        This requires a configuration file being pass through <literal>ldapConfigPath</literal>.
+        This requires a configuration file being pass through `ldapConfigPath`.
       '';
     };
 
     ldapConfigPath = mkOption {
       type = types.path;
       default = "";
-      description = ''
-        Path to the Configuration-File for LDAP-Authentification, will be loaded as <literal>ldap_config.py</literal>.
-        See the <link xlink:href="https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration">documentation</link> for possible options.
+      description = lib.mdDoc ''
+        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.
       '';
     };
   };
 
   config = mkIf cfg.enable {
+    services.netbox.plugins = mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+
     services.redis.servers.netbox.enable = true;
 
     services.postgresql = {
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index a4b886821ebf..90801e996817 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -6,12 +6,19 @@ let
   cfg = config.services.nextcloud;
   fpm = config.services.phpfpm.pools.nextcloud;
 
+  jsonFormat = pkgs.formats.json {};
+
   inherit (cfg) datadir;
 
   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
@@ -69,7 +76,7 @@ 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.
@@ -77,21 +84,56 @@ in {
   ];
 
   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 = "FQDN for the nextcloud instance.";
+      description = lib.mdDoc "FQDN for the nextcloud instance.";
     };
     home = mkOption {
       type = types.str;
       default = "/var/lib/nextcloud";
-      description = "Storage path of nextcloud.";
+      description = lib.mdDoc "Storage path of nextcloud.";
     };
     datadir = mkOption {
       type = types.str;
-      defaultText = "config.services.nextcloud.home";
-      description = ''
-        Data storage path of nextcloud.  Will be <xref linkend="opt-services.nextcloud.home" /> by default.
+      default = config.services.nextcloud.home;
+      defaultText = literalExpression "config.services.nextcloud.home";
+      description = lib.mdDoc ''
+        Data storage path of nextcloud.  Will be [](#opt-services.nextcloud.home) by default.
         This folder will be populated with a config.php and data folder which contains the state of the instance (excl the database).";
       '';
       example = "/mnt/nextcloud-file";
@@ -99,10 +141,10 @@ in {
     extraApps = mkOption {
       type = types.attrsOf types.package;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp.
         The appid must be identical to the "id" value in the apps appinfo/info.xml.
-        Using this will disable the appstore to prevent Nextcloud from updating these apps (see <xref linkend="opt-services.nextcloud.appstoreEnable" />).
+        Using this will disable the appstore to prevent Nextcloud from updating these apps (see [](#opt-services.nextcloud.appstoreEnable)).
       '';
       example = literalExpression ''
         {
@@ -124,8 +166,8 @@ in {
     extraAppsEnable = mkOption {
       type = types.bool;
       default = true;
-      description = ''
-        Automatically enable the apps in <xref linkend="opt-services.nextcloud.extraApps" /> every time nextcloud starts.
+      description = lib.mdDoc ''
+        Automatically enable the apps in [](#opt-services.nextcloud.extraApps) every time nextcloud starts.
         If set to false, apps need to be enabled in the Nextcloud user interface or with nextcloud-occ app:enable.
       '';
     };
@@ -133,33 +175,42 @@ in {
       type = types.nullOr types.bool;
       default = null;
       example = true;
-      description = ''
+      description = lib.mdDoc ''
         Allow the installation of apps and app updates from the store.
-        Enabled by default unless there are packages in <xref linkend="opt-services.nextcloud.extraApps" />.
-        Set to true to force enable the store even if <xref linkend="opt-services.nextcloud.extraApps" /> is used.
+        Enabled by default unless there are packages in [](#opt-services.nextcloud.extraApps).
+        Set to true to force enable the store even if [](#opt-services.nextcloud.extraApps) is used.
         Set to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting.
       '';
     };
     logLevel = mkOption {
       type = types.ints.between 0 4;
       default = 2;
-      description = "Log level value between 0 (DEBUG) and 4 (FATAL).";
+      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;
-      description = "Use https for generated links.";
+      description = lib.mdDoc "Use https for generated links.";
     };
     package = mkOption {
       type = types.package;
-      description = "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud22" "nextcloud23" ];
+      description = lib.mdDoc "Which package to use for the Nextcloud instance.";
+      relatedPackages = [ "nextcloud24" "nextcloud25" ];
     };
     phpPackage = mkOption {
       type = types.package;
-      relatedPackages = [ "php74" "php80" ];
+      relatedPackages = [ "php80" "php81" ];
       defaultText = "pkgs.php";
-      description = ''
+      description = lib.mdDoc ''
         PHP package to use for Nextcloud.
       '';
     };
@@ -167,7 +218,7 @@ in {
     maxUploadSize = mkOption {
       default = "512M";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Defines the upload limit for files. This changes the relevant options
         in php.ini and nginx if enabled.
       '';
@@ -176,7 +227,7 @@ in {
     skeletonDirectory = mkOption {
       default = "";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The directory where the skeleton files are located. These files will be
         copied to the data directory of new users. Leave empty to not copy any
         skeleton files.
@@ -186,7 +237,7 @@ in {
     webfinger = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable this option if you plan on using the webfinger plugin.
         The appropriate nginx rewrite rules will be added to your configuration.
       '';
@@ -196,7 +247,7 @@ in {
       type = with types; functionTo (listOf package);
       default = all: [];
       defaultText = literalExpression "all: []";
-      description = ''
+      description = lib.mdDoc ''
         Additional PHP extensions to use for nextcloud.
         By default, only extensions necessary for a vanilla nextcloud installation are enabled,
         but you may choose from the list of available extensions and add further ones.
@@ -223,7 +274,7 @@ in {
         "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
         catch_workers_output = "yes";
       };
-      description = ''
+      description = lib.mdDoc ''
         Options for PHP's php.ini file for nextcloud.
       '';
     };
@@ -238,16 +289,24 @@ in {
         "pm.max_spare_servers" = "4";
         "pm.max_requests" = "500";
       };
-      description = ''
-        Options for nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+      description = lib.mdDoc ''
+        Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
       '';
     };
 
     poolConfig = mkOption {
       type = types.nullOr types.lines;
       default = null;
-      description = ''
-        Options for nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+      description = lib.mdDoc ''
+        Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
+      '';
+    };
+
+    fastcgiTimeout = mkOption {
+      type = types.int;
+      default = 120;
+      description = lib.mdDoc ''
+        FastCGI timeout for database connection in seconds.
       '';
     };
 
@@ -256,7 +315,7 @@ in {
       createLocally = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        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
@@ -272,72 +331,72 @@ in {
       dbtype = mkOption {
         type = types.enum [ "sqlite" "pgsql" "mysql" ];
         default = "sqlite";
-        description = "Database type.";
+        description = lib.mdDoc "Database type.";
       };
       dbname = mkOption {
         type = types.nullOr types.str;
         default = "nextcloud";
-        description = "Database name.";
+        description = lib.mdDoc "Database name.";
       };
       dbuser = mkOption {
         type = types.nullOr types.str;
         default = "nextcloud";
-        description = "Database user.";
+        description = lib.mdDoc "Database user.";
       };
       dbpassFile = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The full path to a file that contains the database password.
         '';
       };
       dbhost = mkOption {
         type = types.nullOr types.str;
         default = "localhost";
-        description = ''
+        description = lib.mdDoc ''
           Database host.
 
           Note: for using Unix authentication with PostgreSQL, this should be
-          set to <literal>/run/postgresql</literal>.
+          set to `/run/postgresql`.
         '';
       };
       dbport = mkOption {
         type = with types; nullOr (either int str);
         default = null;
-        description = "Database port.";
+        description = lib.mdDoc "Database port.";
       };
       dbtableprefix = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Table prefix in Nextcloud database.";
+        description = lib.mdDoc "Table prefix in Nextcloud database.";
       };
       adminuser = mkOption {
         type = types.str;
         default = "root";
-        description = "Admin username.";
+        description = lib.mdDoc "Admin username.";
       };
       adminpassFile = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The full path to a file that contains the admin's password. Must be
-          readable by user <literal>nextcloud</literal>.
+          readable by user `nextcloud`.
         '';
       };
 
       extraTrustedDomains = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Trusted domains, from which the nextcloud installation will be
-          acessible.  You don't need to add
-          <literal>services.nextcloud.hostname</literal> here.
+          accessible.  You don't need to add
+          `services.nextcloud.hostname` here.
         '';
       };
 
       trustedProxies = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Trusted proxies, to provide if the nextcloud installation is being
           proxied to secure against e.g. spoofing.
         '';
@@ -348,10 +407,10 @@ in {
         default = null;
         example = "https";
 
-        description = ''
+        description = lib.mdDoc ''
           Force Nextcloud to always use HTTPS i.e. for link generation. Nextcloud
           uses the currently used protocol by default, but when behind a reverse-proxy,
-          it may use <literal>http</literal> for everything although Nextcloud
+          it may use `http` for everything although Nextcloud
           may be served via HTTPS.
         '';
       };
@@ -360,78 +419,78 @@ 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";
-            description = ''
+            description = lib.mdDoc ''
               The name of the S3 bucket.
             '';
           };
           autocreate = mkOption {
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Create the objectstore if it does not exist.
             '';
           };
           key = mkOption {
             type = types.str;
             example = "EJ39ITYZEUH5BGWDRUFY";
-            description = ''
+            description = lib.mdDoc ''
               The access key for the S3 bucket.
             '';
           };
           secretFile = mkOption {
             type = types.str;
             example = "/var/nextcloud-objectstore-s3-secret";
-            description = ''
+            description = lib.mdDoc ''
               The full path to a file that contains the access secret. Must be
-              readable by user <literal>nextcloud</literal>.
+              readable by user `nextcloud`.
             '';
           };
           hostname = mkOption {
             type = types.nullOr types.str;
             default = null;
             example = "example.com";
-            description = ''
+            description = lib.mdDoc ''
               Required for some non-Amazon implementations.
             '';
           };
           port = mkOption {
             type = types.nullOr types.port;
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               Required for some non-Amazon implementations.
             '';
           };
           useSsl = mkOption {
             type = types.bool;
             default = true;
-            description = ''
+            description = lib.mdDoc ''
               Use SSL for objectstore access.
             '';
           };
@@ -439,33 +498,33 @@ in {
             type = types.nullOr types.str;
             default = null;
             example = "REGION";
-            description = ''
+            description = lib.mdDoc ''
               Required for some non-Amazon implementations.
             '';
           };
           usePathStyle = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Required for some non-Amazon S3 implementations.
 
               Ordinarily, requests will be made with
-              <literal>http://bucket.hostname.domain/</literal>, but with path style
+              `http://bucket.hostname.domain/`, but with path style
               enabled requests are made with
-              <literal>http://hostname.domain/bucket</literal> instead.
+              `http://hostname.domain/bucket` instead.
             '';
           };
         };
       };
     };
 
-    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;
     };
 
@@ -473,14 +532,14 @@ in {
       apcu = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to load the APCu module into PHP.
         '';
       };
       redis = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to load the Redis module into PHP.
           You still need to enable Redis in your config.php.
           See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
@@ -489,7 +548,7 @@ in {
       memcached = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to load the Memcached module into PHP.
           You still need to enable Memcached in your config.php.
           See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
@@ -500,7 +559,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Run regular auto update of all apps installed from the nextcloud app store.
         '';
       };
@@ -508,54 +567,92 @@ 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.
       '';
     };
 
-    nginx.recommendedHttpHeaders = mkOption {
-      type = types.bool;
-      default = true;
-      description = "Enable additional recommended HTTP response headers";
+    extraOptions = mkOption {
+      type = jsonFormat.type;
+      default = {};
+      description = lib.mdDoc ''
+        Extra options which should be appended to nextcloud's config.php file.
+      '';
+      example = literalExpression '' {
+        redis = {
+          host = "/run/redis/redis.sock";
+          port = 0;
+          dbindex = 0;
+          password = "secret";
+          timeout = 1.5;
+        };
+      } '';
+    };
+
+    secretFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Secret options which will be appended to nextcloud's config.php file (written as JSON, in the same
+        form as the [](#opt-services.nextcloud.extraOptions) option), for example
+        `{"redis":{"password":"secret"}}`.
+      '';
+    };
+
+    nginx = {
+      recommendedHttpHeaders = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Enable additional recommended HTTP response headers";
+      };
+      hstsMaxAge = mkOption {
+        type = types.ints.positive;
+        default = 15552000;
+        description = lib.mdDoc ''
+          Value for the `max-age` directive of the HTTP
+          `Strict-Transport-Security` header.
+
+          See section 6.1.1 of IETF RFC 6797 for detailed information on this
+          directive and header.
+        '';
+      };
     };
   };
 
   config = mkIf cfg.enable (mkMerge [
     { warnings = let
-        latest = 23;
+        latest = 25;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
@@ -588,9 +685,26 @@ in {
           Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
           Please migrate your configuration to config.services.nextcloud.poolSettings.
         '')
-        ++ (optional (versionOlder cfg.package.version "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 (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
+        ++ (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.
+
+          If you don't use that feature, you can switch to OpenSSL 3 and get
+          rid of this warning by declaring
+
+            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 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
@@ -611,16 +725,13 @@ in {
               nextcloud defined in an overlay, please set `services.nextcloud.package` to
               `pkgs.nextcloud`.
             ''
-          else if versionOlder stateVersion "21.11" then nextcloud21
-          else if versionOlder stateVersion "22.05" then nextcloud22
-          else nextcloud23
+          else if versionOlder stateVersion "22.11" then nextcloud24
+          else nextcloud25
         );
 
-      services.nextcloud.datadir = mkOptionDefault config.services.nextcloud.home;
-
       services.nextcloud.phpPackage =
-        if versionOlder cfg.package.version "21" then pkgs.php74
-        else pkgs.php80;
+        if versionOlder cfg.package.version "24" then pkgs.php80
+        else pkgs.php81;
     }
 
     { assertions = [
@@ -631,6 +742,7 @@ in {
 
     { systemd.timers.nextcloud-cron = {
         wantedBy = [ "timers.target" ];
+        after = [ "nextcloud-setup.service" ];
         timerConfig.OnBootSec = "5m";
         timerConfig.OnUnitActiveSec = "5m";
         timerConfig.Unit = "nextcloud-cron.service";
@@ -646,7 +758,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' => [
@@ -687,10 +799,20 @@ in {
                     $file
                   ));
                 }
-
                 return trim(file_get_contents($file));
+              }''}
+            function nix_decode_json_file($file, $error) {
+              if (!file_exists($file)) {
+                throw new \RuntimeException(sprintf($error, $file));
               }
-            ''}
+              $decoded = json_decode(file_get_contents($file), true);
+
+              if (json_last_error() !== JSON_ERROR_NONE) {
+                throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
+              }
+
+              return $decoded;
+            }
             $CONFIG = [
               'apps_paths' => [
                 ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"}
@@ -701,22 +823,39 @@ in {
               'datadirectory' => '${datadir}/data',
               'skeletondirectory' => '${cfg.skeletonDirectory}',
               ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
-              'log_type' => 'syslog',
-              'log_level' => '${builtins.toString cfg.logLevel}',
+              'log_type' => '${cfg.logType}',
+              'loglevel' => '${builtins.toString cfg.logLevel}',
               ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"}
               ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
               ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
               ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"}
               ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
               ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
-              ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_secret('${c.dbpassFile}'),"}
+              ${optionalString (c.dbpassFile != null) ''
+                  'dbpassword' => nix_read_secret(
+                    "${c.dbpassFile}"
+                  ),
+                ''
+              }
               'dbtype' => '${c.dbtype}',
-              'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
-              'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
+              '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}"}
+              ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"}
               ${objectstoreConfig}
             ];
+
+            $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
+              "${jsonFormat.generate "nextcloud-extraOptions.json" cfg.extraOptions}",
+              "impossible: this should never happen (decoding generated extraOptions file %s failed)"
+            ));
+
+            ${optionalString (cfg.secretFile != null) ''
+              $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
+                "${cfg.secretFile}",
+                "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!"
+              ));
+            ''}
           '';
           occInstallCmd = let
             mkExport = { arg, value }: "export ${arg}=${value}";
@@ -740,9 +879,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 ''
@@ -811,7 +950,7 @@ in {
             ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
 
             ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) ''
-                # Try to enable apps (don't fail when one of them cannot be enabled , eg. due to incompatible version)
+                # Try to enable apps
                 ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)}
             ''}
 
@@ -821,12 +960,14 @@ in {
           serviceConfig.User = "nextcloud";
         };
         nextcloud-cron = {
+          after = [ "nextcloud-setup.service" ];
           environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
           serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php";
         };
         nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
+          after = [ "nextcloud-setup.service" ];
           serviceConfig.Type = "oneshot";
           serviceConfig.ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
           serviceConfig.User = "nextcloud";
@@ -871,7 +1012,7 @@ in {
         # 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 = {
+        settings = mkIf (versionOlder cfg.package.version "24") {
           mysqld = {
             innodb_read_only_compressed = 0;
           };
@@ -895,7 +1036,6 @@ in {
             priority = 100;
             extraConfig = ''
               allow all;
-              log_not_found off;
               access_log off;
             '';
           };
@@ -956,7 +1096,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 = ''
@@ -983,7 +1123,9 @@ in {
             add_header X-Permitted-Cross-Domain-Policies none;
             add_header X-Frame-Options sameorigin;
             add_header Referrer-Policy no-referrer;
-            add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+          ''}
+          ${optionalString (cfg.https) ''
+            add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always;
           ''}
           client_max_body_size ${cfg.maxUploadSize};
           fastcgi_buffers 64 4K;
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index 8f55086a2bd1..4207c4008d5b 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -11,7 +11,7 @@
   desktop client is packaged at <literal>pkgs.nextcloud-client</literal>.
  </para>
  <para>
-  The current default by NixOS is <package>nextcloud23</package> which is also the latest
+  The current default by NixOS is <package>nextcloud25</package> which is also the latest
   major version available.
  </para>
  <section xml:id="module-services-nextcloud-basic-usage">
@@ -170,6 +170,20 @@
      </listitem>
     </itemizedlist>
    </listitem>
+   <listitem>
+    <formalpara>
+     <title>Server-side encryption</title>
+     <para>
+      Nextcloud supports <link xlink:href="https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html">server-side encryption (SSE)</link>.
+      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 because this is implemented using the legacy cipher RC4.
+      If <xref linkend="opt-system.stateVersion" /> is <emphasis>above</emphasis> <literal>22.05</literal>,
+      this is disabled by default. To turn it on again and for further information please refer to
+      <xref linkend="opt-services.nextcloud.enableBrokenCiphersForSSE" />.
+     </para>
+    </formalpara>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -269,7 +283,7 @@
 
   <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
+   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
    <literal>&lt;nixpkgs/pkgs/servers/nextcloud/default.nix&gt;</literal>):
 <programlisting>/* ... */
diff --git a/nixos/modules/services/web-apps/nexus.nix b/nixos/modules/services/web-apps/nexus.nix
index dc50a06705f3..1f4a758b87ea 100644
--- a/nixos/modules/services/web-apps/nexus.nix
+++ b/nixos/modules/services/web-apps/nexus.nix
@@ -11,43 +11,43 @@ in
 {
   options = {
     services.nexus = {
-      enable = mkEnableOption "Sonatype Nexus3 OSS service";
+      enable = mkEnableOption (lib.mdDoc "Sonatype Nexus3 OSS service");
 
       package = mkOption {
         type = types.package;
         default = pkgs.nexus;
         defaultText = literalExpression "pkgs.nexus";
-        description = "Package which runs Nexus3";
+        description = lib.mdDoc "Package which runs Nexus3";
       };
 
       user = mkOption {
         type = types.str;
         default = "nexus";
-        description = "User which runs Nexus3.";
+        description = lib.mdDoc "User which runs Nexus3.";
       };
 
       group = mkOption {
         type = types.str;
         default = "nexus";
-        description = "Group which runs Nexus3.";
+        description = lib.mdDoc "Group which runs Nexus3.";
       };
 
       home = mkOption {
         type = types.str;
         default = "/var/lib/sonatype-work";
-        description = "Home directory of the Nexus3 instance.";
+        description = lib.mdDoc "Home directory of the Nexus3 instance.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "Address to listen on.";
+        description = lib.mdDoc "Address to listen on.";
       };
 
       listenPort = mkOption {
         type = types.int;
         default = 8081;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       jvmOpts = mkOption {
@@ -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/nixos/modules/services/web-apps/nifi.nix b/nixos/modules/services/web-apps/nifi.nix
index 21a631272641..f643e24d81d9 100644
--- a/nixos/modules/services/web-apps/nifi.nix
+++ b/nixos/modules/services/web-apps/nifi.nix
@@ -27,31 +27,31 @@ let
 in {
   options = {
     services.nifi = {
-      enable = lib.mkEnableOption "Apache NiFi";
+      enable = lib.mkEnableOption (lib.mdDoc "Apache NiFi");
 
       package = lib.mkOption {
         type = lib.types.package;
         default = pkgs.nifi;
         defaultText = lib.literalExpression "pkgs.nifi";
-        description = "Apache NiFi package to use.";
+        description = lib.mdDoc "Apache NiFi package to use.";
       };
 
       user = lib.mkOption {
         type = lib.types.str;
         default = "nifi";
-        description = "User account where Apache NiFi runs.";
+        description = lib.mdDoc "User account where Apache NiFi runs.";
       };
 
       group = lib.mkOption {
         type = lib.types.str;
         default = "nifi";
-        description = "Group account where Apache NiFi runs.";
+        description = lib.mdDoc "Group account where Apache NiFi runs.";
       };
 
       enableHTTPS = lib.mkOption {
         type = lib.types.bool;
         default = true;
-        description = "Enable HTTPS protocol. Don`t use in production.";
+        description = lib.mdDoc "Enable HTTPS protocol. Don`t use in production.";
       };
 
       listenHost = lib.mkOption {
@@ -62,7 +62,7 @@ in {
           then "0.0.0.0"
           else "127.0.0.1"
         '';
-        description = "Bind to an ip for Apache NiFi web-ui.";
+        description = lib.mdDoc "Bind to an ip for Apache NiFi web-ui.";
       };
 
       listenPort = lib.mkOption {
@@ -73,7 +73,7 @@ in {
           then "8443"
           else "8000"
         '';
-        description = "Bind to a port for Apache NiFi web-ui.";
+        description = lib.mdDoc "Bind to a port for Apache NiFi web-ui.";
       };
 
       proxyHost = lib.mkOption {
@@ -84,7 +84,7 @@ in {
           then "0.0.0.0"
           else null
         '';
-        description = "Allow requests from a specific host.";
+        description = lib.mdDoc "Allow requests from a specific host.";
       };
 
       proxyPort = lib.mkOption {
@@ -95,34 +95,34 @@ in {
           then "8443"
           else null
         '';
-        description = "Allow requests from a specific port.";
+        description = lib.mdDoc "Allow requests from a specific port.";
       };
 
       initUser = lib.mkOption {
         type = lib.types.nullOr lib.types.str;
         default = null;
-        description = "Initial user account for Apache NiFi. Username must be at least 4 characters.";
+        description = lib.mdDoc "Initial user account for Apache NiFi. Username must be at least 4 characters.";
       };
 
       initPasswordFile = lib.mkOption {
         type = lib.types.nullOr lib.types.path;
         default = null;
         example = "/run/keys/nifi/password-nifi";
-        description = "nitial password for Apache NiFi. Password must be at least 12 characters.";
+        description = lib.mdDoc "nitial password for Apache NiFi. Password must be at least 12 characters.";
       };
 
       initJavaHeapSize = lib.mkOption {
         type = lib.types.nullOr lib.types.int;
         default = null;
         example = 1024;
-        description = "Set the initial heap size for the JVM in MB.";
+        description = lib.mdDoc "Set the initial heap size for the JVM in MB.";
       };
 
       maxJavaHeapSize = lib.mkOption {
         type = lib.types.nullOr lib.types.int;
         default = null;
         example = 2048;
-        description = "Set the initial heap size for the JVM in MB.";
+        description = lib.mdDoc "Set the initial heap size for the JVM in MB.";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/node-red.nix b/nixos/modules/services/web-apps/node-red.nix
index 4512907f027b..f4d4ad9681a6 100644
--- a/nixos/modules/services/web-apps/node-red.nix
+++ b/nixos/modules/services/web-apps/node-red.nix
@@ -17,19 +17,19 @@ 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;
       defaultText = literalExpression "pkgs.nodePackages.node-red";
       type = types.package;
-      description = "Node-RED package to use.";
+      description = lib.mdDoc "Node-RED package to use.";
     };
 
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Open ports in the firewall for the server.
       '';
     };
@@ -37,7 +37,7 @@ in
     withNpmAndGcc = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Give Node-RED access to NPM and GCC at runtime, so 'Nodes' can be
         downloaded and managed imperatively via the 'Palette Manager'.
       '';
@@ -47,10 +47,9 @@ in
       type = types.path;
       default = "${cfg.package}/lib/node_modules/node-red/settings.js";
       defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/settings.js"'';
-      description = ''
+      description = lib.mdDoc ''
         Path to the JavaScript configuration file.
-        See <link
-        xlink:href="https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js"/>
+        See <https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js>
         for a configuration example.
       '';
     };
@@ -58,13 +57,13 @@ in
     port = mkOption {
       type = types.port;
       default = 1880;
-      description = "Listening port.";
+      description = lib.mdDoc "Listening port.";
     };
 
     user = mkOption {
       type = types.str;
       default = defaultUser;
-      description = ''
+      description = lib.mdDoc ''
         User under which Node-RED runs.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.
@@ -74,7 +73,7 @@ in
     group = mkOption {
       type = types.str;
       default = defaultUser;
-      description = ''
+      description = lib.mdDoc ''
         Group under which Node-RED runs.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.
@@ -84,7 +83,7 @@ in
     userDir = mkOption {
       type = types.path;
       default = "/var/lib/node-red";
-      description = ''
+      description = lib.mdDoc ''
         The directory to store all user data, such as flow and credential files and all library data. If left
         as the default value this directory will automatically be created before the node-red service starts,
         otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership
@@ -95,13 +94,13 @@ in
     safe = mkOption {
       type = types.bool;
       default = false;
-      description = "Whether to launch Node-RED in --safe mode.";
+      description = lib.mdDoc "Whether to launch Node-RED in --safe mode.";
     };
 
     define = mkOption {
       type = types.attrs;
       default = {};
-      description = "List of settings.js overrides to pass via -D to Node-RED.";
+      description = lib.mdDoc "List of settings.js overrides to pass via -D to Node-RED.";
       example = literalExpression ''
         {
           "logging.console.level" = "trace";
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
new file mode 100644
index 000000000000..79ed3e43dd18
--- /dev/null
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -0,0 +1,291 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.onlyoffice;
+in
+{
+  options.services.onlyoffice = {
+    enable = mkEnableOption (lib.mdDoc "OnlyOffice DocumentServer");
+
+    enableExampleServer = mkEnableOption (lib.mdDoc "OnlyOffice example server");
+
+    hostname = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc "FQDN for the onlyoffice instance.";
+    };
+
+    jwtSecretFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Path to a file that contains the secret to sign web requests using JSON Web Tokens.
+        If left at the default value null signing is disabled.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.onlyoffice-documentserver;
+      defaultText = lib.literalExpression "pkgs.onlyoffice-documentserver";
+      description = lib.mdDoc "Which package to use for the OnlyOffice instance.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8000;
+      description = lib.mdDoc "Port the OnlyOffice DocumentServer should listens on.";
+    };
+
+    examplePort = mkOption {
+      type = types.port;
+      default = null;
+      description = lib.mdDoc "Port the OnlyOffice Example server should listens on.";
+    };
+
+    postgresHost = mkOption {
+      type = types.str;
+      default = "/run/postgresql";
+      description = lib.mdDoc "The Postgresql hostname or socket path OnlyOffice should connect to.";
+    };
+
+    postgresName = mkOption {
+      type = types.str;
+      default = "onlyoffice";
+      description = lib.mdDoc "The name of database OnlyOffice should user.";
+    };
+
+    postgresPasswordFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Path to a file that contains the password OnlyOffice should use to connect to Postgresql.
+        Unused when using socket authentication.
+      '';
+    };
+
+    postgresUser = mkOption {
+      type = types.str;
+      default = "onlyoffice";
+      description = lib.mdDoc ''
+        The username OnlyOffice should use to connect to Postgresql.
+        Unused when using socket authentication.
+      '';
+    };
+
+    rabbitmqUrl = mkOption {
+      type = types.str;
+      default = "amqp://guest:guest@localhost:5672";
+      description = lib.mdDoc "The Rabbitmq in amqp URI style OnlyOffice should connect to.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services = {
+      nginx = {
+        enable = mkDefault true;
+        # misses text/csv, font/ttf, application/x-font-ttf, application/rtf, application/wasm
+        recommendedGzipSettings = mkDefault true;
+        recommendedProxySettings = mkDefault true;
+
+        upstreams = {
+          # /etc/nginx/includes/http-common.conf
+          onlyoffice-docservice = {
+            servers = { "localhost:${toString cfg.port}" = { }; };
+          };
+          onlyoffice-example = lib.mkIf cfg.enableExampleServer {
+            servers = { "localhost:${toString cfg.examplePort}" = { }; };
+          };
+        };
+
+        virtualHosts.${cfg.hostname} = {
+          locations = {
+            # /etc/nginx/includes/ds-docservice.conf
+            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps\/apps\/api\/documents\/api\.js)$".extraConfig = ''
+              expires -1;
+              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2;
+            '';
+            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps)(\/.*\.json)$".extraConfig = ''
+              expires 365d;
+              error_log /dev/null crit;
+              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
+            '';
+            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(sdkjs-plugins)(\/.*\.json)$".extraConfig = ''
+              expires 365d;
+              error_log /dev/null crit;
+              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
+            '';
+            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(web-apps|sdkjs|sdkjs-plugins|fonts)(\/.*)$".extraConfig = ''
+              expires 365d;
+              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
+            '';
+            "~* ^(\/cache\/files.*)(\/.*)".extraConfig = ''
+              alias /var/lib/onlyoffice/documentserver/App_Data$1;
+              add_header Content-Disposition "attachment; filename*=UTF-8''$arg_filename";
+
+              set $secret_string verysecretstring;
+              secure_link $arg_md5,$arg_expires;
+              secure_link_md5 "$secure_link_expires$uri$secret_string";
+
+              if ($secure_link = "") {
+                return 403;
+              }
+
+              if ($secure_link = "0") {
+                return 410;
+              }
+            '';
+            "~* ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(internal)(\/.*)$".extraConfig = ''
+              allow 127.0.0.1;
+              deny all;
+              proxy_pass http://onlyoffice-docservice/$2$3;
+            '';
+            "~* ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(info)(\/.*)$".extraConfig = ''
+              allow 127.0.0.1;
+              deny all;
+              proxy_pass http://onlyoffice-docservice/$2$3;
+            '';
+            "/".extraConfig = ''
+              proxy_pass http://onlyoffice-docservice;
+            '';
+            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?(\/doc\/.*)".extraConfig = ''
+              proxy_pass http://onlyoffice-docservice$2;
+              proxy_http_version 1.1;
+            '';
+            "/${cfg.package.version}/".extraConfig = ''
+              proxy_pass http://onlyoffice-docservice/;
+            '';
+            "~ ^(\/[\d]+\.[\d]+\.[\d]+[\.|-][\d]+)?\/(dictionaries)(\/.*)$".extraConfig = ''
+              expires 365d;
+              alias ${cfg.package}/var/www/onlyoffice/documentserver/$2$3;
+            '';
+            # /etc/nginx/includes/ds-example.conf
+            "~ ^(\/welcome\/.*)$".extraConfig = ''
+              expires 365d;
+              alias ${cfg.package}/var/www/onlyoffice/documentserver-example$1;
+              index docker.html;
+            '';
+            "/example/".extraConfig = lib.mkIf cfg.enableExampleServer ''
+              proxy_pass http://onlyoffice-example/;
+              proxy_set_header X-Forwarded-Path /example;
+            '';
+          };
+          extraConfig = ''
+            rewrite ^/$ /welcome/ redirect;
+            rewrite ^\/OfficeWeb(\/apps\/.*)$ /${cfg.package.version}/web-apps$1 redirect;
+            rewrite ^(\/web-apps\/apps\/(?!api\/).*)$ /${cfg.package.version}$1 redirect;
+
+            # based on https://github.com/ONLYOFFICE/document-server-package/blob/master/common/documentserver/nginx/includes/http-common.conf.m4#L29-L34
+            # without variable indirection and correct variable names
+            proxy_set_header Host $host;
+            proxy_set_header X-Forwarded-Host $host;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            # required for CSP to take effect
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            # required for websocket
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection $connection_upgrade;
+          '';
+        };
+      };
+
+      rabbitmq.enable = lib.mkDefault true;
+
+      postgresql = {
+        enable = lib.mkDefault true;
+        ensureDatabases = [ "onlyoffice" ];
+        ensureUsers = [{
+          name = "onlyoffice";
+          ensurePermissions = { "DATABASE \"onlyoffice\"" = "ALL PRIVILEGES"; };
+        }];
+      };
+    };
+
+    systemd.services = {
+      onlyoffice-converter = {
+        description = "onlyoffice converter";
+        after = [ "network.target" "onlyoffice-docservice.service" "postgresql.service" ];
+        requires = [ "network.target" "onlyoffice-docservice.service" "postgresql.service" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper FileConverter/converter /run/onlyoffice/config";
+          Group = "onlyoffice";
+          Restart = "always";
+          RuntimeDirectory = "onlyoffice";
+          StateDirectory = "onlyoffice";
+          Type = "simple";
+          User = "onlyoffice";
+        };
+      };
+
+      onlyoffice-docservice =
+        let
+          onlyoffice-prestart = pkgs.writeShellScript "onlyoffice-prestart" ''
+            PATH=$PATH:${lib.makeBinPath (with pkgs; [ jq moreutils config.services.postgresql.package ])}
+            umask 077
+            mkdir -p /run/onlyoffice/config/ /var/lib/onlyoffice/documentserver/sdkjs/{slide/themes,common}/ /var/lib/onlyoffice/documentserver/{fonts,server/FileConverter/bin}/
+            cp -r ${cfg.package}/etc/onlyoffice/documentserver/* /run/onlyoffice/config/
+            chmod u+w /run/onlyoffice/config/default.json
+
+            cp /run/onlyoffice/config/default.json{,.orig}
+
+            # for a mapping of environment variables from the docker container to json options see
+            # https://github.com/ONLYOFFICE/Docker-DocumentServer/blob/master/run-document-server.sh
+            jq '
+              .services.CoAuthoring.server.port = ${toString cfg.port} |
+              .services.CoAuthoring.sql.dbHost = "${cfg.postgresHost}" |
+              .services.CoAuthoring.sql.dbName = "${cfg.postgresName}" |
+            ${lib.optionalString (cfg.postgresPasswordFile != null) ''
+              .services.CoAuthoring.sql.dbPass = "'"$(cat ${cfg.postgresPasswordFile})"'" |
+            ''}
+              .services.CoAuthoring.sql.dbUser = "${cfg.postgresUser}" |
+            ${lib.optionalString (cfg.jwtSecretFile != null) ''
+              .services.CoAuthoring.token.enable.browser = true |
+              .services.CoAuthoring.token.enable.request.inbox = true |
+              .services.CoAuthoring.token.enable.request.outbox = true |
+              .services.CoAuthoring.secret.inbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
+              .services.CoAuthoring.secret.outbox.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
+              .services.CoAuthoring.secret.session.string = "'"$(cat ${cfg.jwtSecretFile})"'" |
+            ''}
+              .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
+              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
+          '';
+        in
+        {
+          description = "onlyoffice documentserver";
+          after = [ "network.target" "postgresql.service" ];
+          requires = [ "postgresql.service" ];
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            ExecStart = "${cfg.package.fhs}/bin/onlyoffice-wrapper DocService/docservice /run/onlyoffice/config";
+            ExecStartPre = onlyoffice-prestart;
+            Group = "onlyoffice";
+            Restart = "always";
+            RuntimeDirectory = "onlyoffice";
+            StateDirectory = "onlyoffice";
+            Type = "simple";
+            User = "onlyoffice";
+          };
+        };
+    };
+
+    users.users = {
+      onlyoffice = {
+        description = "OnlyOffice Service";
+        group = "onlyoffice";
+        isSystemUser = true;
+      };
+    };
+
+    users.groups.onlyoffice = { };
+  };
+}
diff --git a/nixos/modules/services/web-apps/openwebrx.nix b/nixos/modules/services/web-apps/openwebrx.nix
index 9e90c01e0bbb..72c5d6c7818c 100644
--- a/nixos/modules/services/web-apps/openwebrx.nix
+++ b/nixos/modules/services/web-apps/openwebrx.nix
@@ -4,13 +4,13 @@ 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;
       default = pkgs.openwebrx;
       defaultText = literalExpression "pkgs.openwebrx";
-      description = "OpenWebRX package to use for the service";
+      description = lib.mdDoc "OpenWebRX package to use for the service";
     };
   };
 
@@ -19,6 +19,10 @@ in
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [
         csdr
+        digiham
+        codec2
+        js8call
+        m17-cxx-demod
         alsaUtils
         netcat
       ];
diff --git a/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix
new file mode 100644
index 000000000000..b72dd8243bb4
--- /dev/null
+++ b/nixos/modules/services/web-apps/outline.nix
@@ -0,0 +1,788 @@
+{ 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 one occurrence 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 CLI, the DATABASE_URL environment
+        # variable is read by the app.
+        ${if (cfg.databaseUrl == "local") then ''
+          cat <<EOF > $RUNTIME_DIRECTORY/database.json
+          {
+            "production": {
+              "dialect": "postgres",
+              "host": "/run/postgresql",
+              "username": null,
+              "password": null
+            }
+          }
+          EOF
+          export DATABASE_URL=${lib.escapeShellArg localPostgresqlUrl}
+          export PGSSLMODE=disable
+        '' 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
+          export DATABASE_URL=${lib.escapeShellArg cfg.databaseUrl}
+        ''}
+
+        cd $RUNTIME_DIRECTORY
+        ${sequelize}/bin/outline-sequelize db:migrate
+      '';
+
+      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/build";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/peering-manager.nix b/nixos/modules/services/web-apps/peering-manager.nix
new file mode 100644
index 000000000000..666b82621268
--- /dev/null
+++ b/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/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index e195e6e6e824..4dbcb09d2ae2 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -11,6 +11,7 @@ let
     NODE_CONFIG_DIR = "/var/lib/peertube/config";
     NODE_ENV = "production";
     NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
+    NPM_CONFIG_CACHE = "/var/cache/peertube/.npm";
     NPM_CONFIG_PREFIX = cfg.package;
     HOME = cfg.package;
   };
@@ -66,58 +67,68 @@ 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;
       default = "peertube";
-      description = "User account under which Peertube runs.";
+      description = lib.mdDoc "User account under which Peertube runs.";
     };
 
     group = lib.mkOption {
       type = lib.types.str;
       default = "peertube";
-      description = "Group under which Peertube runs.";
+      description = lib.mdDoc "Group under which Peertube runs.";
     };
 
     localDomain = lib.mkOption {
       type = lib.types.str;
       example = "peertube.example.com";
-      description = "The domain serving your PeerTube instance.";
+      description = lib.mdDoc "The domain serving your PeerTube instance.";
     };
 
     listenHttp = lib.mkOption {
-      type = lib.types.int;
+      type = lib.types.port;
       default = 9000;
-      description = "listen port for HTTP server.";
+      description = lib.mdDoc "listen port for HTTP server.";
     };
 
     listenWeb = lib.mkOption {
-      type = lib.types.int;
+      type = lib.types.port;
       default = 9000;
-      description = "listen port for WEB server.";
+      description = lib.mdDoc "listen port for WEB server.";
     };
 
     enableWebHttps = lib.mkOption {
       type = lib.types.bool;
       default = false;
-      description = "Enable or disable HTTPS protocol.";
+      description = lib.mdDoc "Enable or disable HTTPS protocol.";
     };
 
     dataDirs = lib.mkOption {
       type = lib.types.listOf lib.types.path;
       default = [ ];
       example = [ "/opt/peertube/storage" "/var/cache/peertube" ];
-      description = "Allow access to custom data locations.";
+      description = lib.mdDoc "Allow access to custom data locations.";
     };
 
     serviceEnvironmentFile = lib.mkOption {
       type = lib.types.nullOr lib.types.path;
       default = null;
       example = "/run/keys/peertube/password-init-root";
-      description = ''
+      description = lib.mdDoc ''
         Set environment variables for the service. Mainly useful for setting the initial root password.
         For example write to file:
         PT_INITIAL_ROOT_PASSWORD=changeme
@@ -141,14 +152,20 @@ in {
           };
         }
       '';
-      description = "Configuration for peertube.";
+      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.";
     };
 
     database = {
       createLocally = lib.mkOption {
         type = lib.types.bool;
         default = false;
-        description = "Configure local PostgreSQL database server for PeerTube.";
+        description = lib.mdDoc "Configure local PostgreSQL database server for PeerTube.";
       };
 
       host = lib.mkOption {
@@ -160,32 +177,32 @@ in {
           else null
         '';
         example = "192.168.15.47";
-        description = "Database host address or unix socket.";
+        description = lib.mdDoc "Database host address or unix socket.";
       };
 
       port = lib.mkOption {
-        type = lib.types.int;
+        type = lib.types.port;
         default = 5432;
-        description = "Database host port.";
+        description = lib.mdDoc "Database host port.";
       };
 
       name = lib.mkOption {
         type = lib.types.str;
         default = "peertube";
-        description = "Database name.";
+        description = lib.mdDoc "Database name.";
       };
 
       user = lib.mkOption {
         type = lib.types.str;
         default = "peertube";
-        description = "Database user.";
+        description = lib.mdDoc "Database user.";
       };
 
       passwordFile = lib.mkOption {
         type = lib.types.nullOr lib.types.path;
         default = null;
         example = "/run/keys/peertube/password-posgressql-db";
-        description = "Password for PostgreSQL database.";
+        description = lib.mdDoc "Password for PostgreSQL database.";
       };
     };
 
@@ -193,7 +210,7 @@ in {
       createLocally = lib.mkOption {
         type = lib.types.bool;
         default = false;
-        description = "Configure local Redis server for PeerTube.";
+        description = lib.mdDoc "Configure local Redis server for PeerTube.";
       };
 
       host = lib.mkOption {
@@ -204,32 +221,32 @@ in {
           then "127.0.0.1"
           else null
         '';
-        description = "Redis host.";
+        description = lib.mdDoc "Redis host.";
       };
 
       port = lib.mkOption {
         type = lib.types.nullOr lib.types.port;
-        default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 6379;
+        default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638;
         defaultText = lib.literalExpression ''
           if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket}
           then null
           else 6379
         '';
-        description = "Redis port.";
+        description = lib.mdDoc "Redis port.";
       };
 
       passwordFile = lib.mkOption {
         type = lib.types.nullOr lib.types.path;
         default = null;
         example = "/run/keys/peertube/password-redis-db";
-        description = "Password for redis database.";
+        description = lib.mdDoc "Password for redis database.";
       };
 
       enableUnixSocket = lib.mkOption {
         type = lib.types.bool;
         default = cfg.redis.createLocally;
         defaultText = lib.literalExpression "config.${opt.redis.createLocally}";
-        description = "Use Unix socket.";
+        description = lib.mdDoc "Use Unix socket.";
       };
     };
 
@@ -237,14 +254,14 @@ in {
       createLocally = lib.mkOption {
         type = lib.types.bool;
         default = false;
-        description = "Configure local Postfix SMTP server for PeerTube.";
+        description = lib.mdDoc "Configure local Postfix SMTP server for PeerTube.";
       };
 
       passwordFile = lib.mkOption {
         type = lib.types.nullOr lib.types.path;
         default = null;
         example = "/run/keys/peertube/password-smtp";
-        description = "Password for smtp server.";
+        description = lib.mdDoc "Password for smtp server.";
       };
     };
 
@@ -252,7 +269,7 @@ in {
       type = lib.types.package;
       default = pkgs.peertube;
       defaultText = lib.literalExpression "pkgs.peertube";
-      description = "Peertube package to use.";
+      description = lib.mdDoc "Peertube package to use.";
     };
   };
 
@@ -344,18 +361,20 @@ in {
           };
         };
       }
-      (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis/redis.sock"; }; })
+      (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis-peertube/redis.sock"; }; })
     ];
 
     systemd.tmpfiles.rules = [
       "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
       "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
+      "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" ''
@@ -384,7 +403,9 @@ 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" ];
 
@@ -409,8 +430,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 = {
@@ -425,6 +449,9 @@ in {
         # State directory and mode
         StateDirectory = "peertube";
         StateDirectoryMode = "0750";
+        # Cache directory and mode
+        CacheDirectory = "peertube";
+        CacheDirectoryMode = "0750";
         # Access write directories
         ReadWritePaths = cfg.dataDirs;
         # Environment
@@ -437,17 +464,300 @@ in {
       } // cfgService;
     };
 
+    services.nginx = lib.mkIf cfg.configureNginx {
+      enable = true;
+      virtualHosts."${cfg.localDomain}" = {
+        root = "/var/lib/peertube";
+
+        # 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."@api_websocket" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1230;
+
+          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 = "/www/client-overrides/$1 /www/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|banners)/" = {
+          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;
+            rewrite ^/lazy-static/banners/(.*)$         /$1 break;
+          '';
+        };
+
+        locations."^~ /lazy-static/previews/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.previews;
+          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/previews/(.*)$        /$1 break;
+          '';
+        };
+
+        locations."^~ /static/thumbnails/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.thumbnails;
+          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 ^/static/thumbnails/(.*)$           /$1 break;
+          '';
+        };
+
+        locations."^~ /static/redundancy/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.redundancy;
+          priority = 1360;
+          extraConfig = ''
+            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_after                            5M;
+
+            set $peertube_limit_rate                    800k;
+            set $limit_rate                             $peertube_limit_rate;
+
+            rewrite ^/static/redundancy/(.*)$           /$1 break;
+          '';
+        };
+
+        locations."^~ /static/streaming-playlists/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.streaming_playlists;
+          priority = 1370;
+          extraConfig = ''
+            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_after                            5M;
+
+            set $peertube_limit_rate                    5M;
+            set $limit_rate                             $peertube_limit_rate;
+
+            rewrite ^/static/streaming-playlists/(.*)$  /$1 break;
+          '';
+        };
+
+        locations."~ ^/static/webseed/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.videos;
+          priority = 1380;
+          extraConfig = ''
+            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_after                            5M;
+
+            set $peertube_limit_rate                    800k;
+            set $limit_rate                             $peertube_limit_rate;
+
+            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;
     };
 
-    services.redis = lib.mkMerge [
+    services.redis.servers.peertube = lib.mkMerge [
       (lib.mkIf cfg.redis.createLocally {
         enable = true;
       })
+      (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
+        bind = "127.0.0.1";
+        port = cfg.redis.port;
+      })
       (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
-        unixSocket = "/run/redis/redis.sock";
-        unixSocketPerm = 770;
+        unixSocket = "/run/redis-peertube/redis.sock";
+        unixSocketPerm = 660;
       })
     ];
 
@@ -465,11 +775,13 @@ in {
         };
       })
       (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ])
-      (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis" ];})
+      (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/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
index faf0ce13238e..dd51bacd75ea 100644
--- a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ b/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/nixos/modules/services/web-apps/phylactery.nix b/nixos/modules/services/web-apps/phylactery.nix
new file mode 100644
index 000000000000..4801bd203b48
--- /dev/null
+++ b/nixos/modules/services/web-apps/phylactery.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let cfg = config.services.phylactery;
+in {
+  options.services.phylactery = {
+    enable = mkEnableOption (lib.mdDoc "Whether to enable Phylactery server");
+
+    host = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc "Listen host for Phylactery";
+    };
+
+    port = mkOption {
+      type = types.port;
+      description = lib.mdDoc "Listen port for Phylactery";
+    };
+
+    library = mkOption {
+      type = types.path;
+      description = lib.mdDoc "Path to CBZ library";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.phylactery;
+      defaultText = literalExpression "pkgs.phylactery";
+      description = lib.mdDoc "The Phylactery package to use";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.phylactery = {
+      environment = {
+        PHYLACTERY_ADDRESS = "${cfg.host}:${toString cfg.port}";
+        PHYLACTERY_LIBRARY = "${cfg.library}";
+      };
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ConditionPathExists = cfg.library;
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/phylactery";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ McSinyx ];
+}
diff --git a/nixos/modules/services/web-apps/pict-rs.nix b/nixos/modules/services/web-apps/pict-rs.nix
index e1847fbd5314..ee9ff9b484f6 100644
--- a/nixos/modules/services/web-apps/pict-rs.nix
+++ b/nixos/modules/services/web-apps/pict-rs.nix
@@ -10,25 +10,25 @@ in
   meta.doc = ./pict-rs.xml;
 
   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";
-      description = ''
+      description = lib.mdDoc ''
         The directory where to store the uploaded images.
       '';
     };
     address = mkOption {
       type = types.str;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         The IPv4 address to deploy the service to.
       '';
     };
     port = mkOption {
       type = types.port;
       default = 8080;
-      description = ''
+      description = lib.mdDoc ''
         The port which to bind the service to.
       '';
     };
diff --git a/nixos/modules/services/web-apps/plantuml-server.nix b/nixos/modules/services/web-apps/plantuml-server.nix
index 9ea37b8a4cad..5ebee48c3e0b 100644
--- a/nixos/modules/services/web-apps/plantuml-server.nix
+++ b/nixos/modules/services/web-apps/plantuml-server.nix
@@ -11,13 +11,13 @@ in
 {
   options = {
     services.plantuml-server = {
-      enable = mkEnableOption "PlantUML server";
+      enable = mkEnableOption (lib.mdDoc "PlantUML server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.plantuml-server;
         defaultText = literalExpression "pkgs.plantuml-server";
-        description = "PlantUML server package to use";
+        description = lib.mdDoc "PlantUML server package to use";
       };
 
       packages = {
@@ -25,75 +25,75 @@ in
           type = types.package;
           default = pkgs.jdk;
           defaultText = literalExpression "pkgs.jdk";
-          description = "JDK package to use for the server";
+          description = lib.mdDoc "JDK package to use for the server";
         };
         jetty = mkOption {
           type = types.package;
           default = pkgs.jetty;
           defaultText = literalExpression "pkgs.jetty";
-          description = "Jetty package to use for the server";
+          description = lib.mdDoc "Jetty package to use for the server";
         };
       };
 
       user = mkOption {
         type = types.str;
         default = "plantuml";
-        description = "User which runs PlantUML server.";
+        description = lib.mdDoc "User which runs PlantUML server.";
       };
 
       group = mkOption {
         type = types.str;
         default = "plantuml";
-        description = "Group which runs PlantUML server.";
+        description = lib.mdDoc "Group which runs PlantUML server.";
       };
 
       home = mkOption {
         type = types.str;
         default = "/var/lib/plantuml";
-        description = "Home directory of the PlantUML server instance.";
+        description = lib.mdDoc "Home directory of the PlantUML server instance.";
       };
 
       listenHost = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = "Host to listen on.";
+        description = lib.mdDoc "Host to listen on.";
       };
 
       listenPort = mkOption {
         type = types.int;
         default = 8080;
-        description = "Port to listen on.";
+        description = lib.mdDoc "Port to listen on.";
       };
 
       plantumlLimitSize = mkOption {
         type = types.int;
         default = 4096;
-        description = "Limits image width and height.";
+        description = lib.mdDoc "Limits image width and height.";
       };
 
       graphvizPackage = mkOption {
         type = types.package;
         default = pkgs.graphviz;
         defaultText = literalExpression "pkgs.graphviz";
-        description = "Package containing the dot executable.";
+        description = lib.mdDoc "Package containing the dot executable.";
       };
 
       plantumlStats = mkOption {
         type = types.bool;
         default = false;
-        description = "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
+        description = lib.mdDoc "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
       };
 
       httpAuthorization = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
+        description = lib.mdDoc "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
       };
 
       allowPlantumlInclude = mkOption {
         type = types.bool;
         default = false;
-        description = "Enables !include processing which can read files from the server into diagrams. Files are read relative to the current working directory.";
+        description = lib.mdDoc "Enables !include processing which can read files from the server into diagrams. Files are read relative to the current working directory.";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
index 5d550ae5ca86..e5dc1b103601 100644
--- a/nixos/modules/services/web-apps/plausible.nix
+++ b/nixos/modules/services/web-apps/plausible.nix
@@ -7,11 +7,11 @@ let
 
 in {
   options.services.plausible = {
-    enable = mkEnableOption "plausible";
+    enable = mkEnableOption (lib.mdDoc "plausible");
 
     releaseCookiePath = mkOption {
       type = with types; either str path;
-      description = ''
+      description = lib.mdDoc ''
         The path to the file with release cookie. (used for remote connection to the running node).
       '';
     };
@@ -20,7 +20,7 @@ in {
       name = mkOption {
         default = "admin";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Name of the admin user that plausible will created on initial startup.
         '';
       };
@@ -28,46 +28,46 @@ in {
       email = mkOption {
         type = types.str;
         example = "admin@localhost";
-        description = ''
+        description = lib.mdDoc ''
           Email-address of the admin-user.
         '';
       };
 
       passwordFile = mkOption {
         type = types.either types.str types.path;
-        description = ''
+        description = lib.mdDoc ''
           Path to the file which contains the password of the admin user.
         '';
       };
 
-      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;
-          description = ''
+          description = lib.mdDoc ''
             Name of the database to use.
           '';
         };
         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`.
           '';
         };
       };
@@ -77,35 +77,35 @@ in {
       disableRegistration = mkOption {
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to prohibit creating an account in plausible's UI.
         '';
       };
       secretKeybaseFile = mkOption {
         type = types.either types.path types.str;
-        description = ''
-          Path to the secret used by the <literal>phoenix</literal>-framework. Instructions
+        description = lib.mdDoc ''
+          Path to the secret used by the `phoenix`-framework. Instructions
           how to generate one are documented in the
-          <link xlink:href="https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content">
-          framework docs</link>.
+          [
+          framework docs](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content).
         '';
       };
       port = mkOption {
         default = 8000;
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           Port where the service should be available.
         '';
       };
       baseUrl = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Public URL where plausible is available.
 
-          Note that <literal>/path</literal> components are currently ignored:
-          <link xlink:href="https://github.com/plausible/analytics/issues/1182">
+          Note that `/path` components are currently ignored:
+          [
             https://github.com/plausible/analytics/issues/1182
-          </link>.
+          ](https://github.com/plausible/analytics/issues/1182).
         '';
       };
     };
@@ -114,8 +114,8 @@ in {
       email = mkOption {
         default = "hello@plausible.local";
         type = types.str;
-        description = ''
-          The email id to use for as <emphasis>from</emphasis> address of all communications
+        description = lib.mdDoc ''
+          The email id to use for as *from* address of all communications
           from Plausible.
         '';
       };
@@ -123,36 +123,36 @@ in {
         hostAddr = mkOption {
           default = "localhost";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The host address of your smtp server.
           '';
         };
         hostPort = mkOption {
           default = 25;
           type = types.port;
-          description = ''
+          description = lib.mdDoc ''
             The port of your smtp server.
           '';
         };
         user = mkOption {
           default = null;
           type = types.nullOr types.str;
-          description = ''
+          description = lib.mdDoc ''
             The username/email in case SMTP auth is enabled.
           '';
         };
         passwordFile = mkOption {
           default = null;
           type = with types; nullOr (either str path);
-          description = ''
+          description = lib.mdDoc ''
             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;
-          description = ''
+          description = lib.mdDoc ''
             Number of retries to make until mailer gives up.
           '';
         };
@@ -188,7 +188,11 @@ in {
           inherit (pkgs.plausible.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"
diff --git a/nixos/modules/services/web-apps/powerdns-admin.nix b/nixos/modules/services/web-apps/powerdns-admin.nix
index 4661ba80c5d6..e9f7f41055e1 100644
--- a/nixos/modules/services/web-apps/powerdns-admin.nix
+++ b/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;
@@ -27,7 +27,7 @@ in
       example = literalExpression ''
         [ "-b" "127.0.0.1:8000" ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra arguments passed to powerdns-admin.
       '';
     };
@@ -40,9 +40,9 @@ in
         PORT = 8000
         SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin@/powerdnsadmin?host=/run/postgresql'
       '';
-      description = ''
+      description = lib.mdDoc ''
         Configuration python file.
-        See <link xlink:href="https://github.com/ngoduykhanh/PowerDNS-Admin/blob/v${pkgs.powerdns-admin.version}/configs/development.py">the example configuration</link>
+        See [the example configuration](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/v${pkgs.powerdns-admin.version}/configs/development.py)
         for options.
       '';
     };
@@ -50,7 +50,7 @@ in
     secretKeyFile = mkOption {
       type = types.nullOr types.path;
       example = "/etc/powerdns-admin/secret";
-      description = ''
+      description = lib.mdDoc ''
         The secret used to create cookies.
         This needs to be set, otherwise the default is used and everyone can forge valid login cookies.
         Set this to null to ignore this setting and configure it through another way.
@@ -60,7 +60,7 @@ in
     saltFile = mkOption {
       type = types.nullOr types.path;
       example = "/etc/powerdns-admin/salt";
-      description = ''
+      description = lib.mdDoc ''
         The salt used for serialization.
         This should be set, otherwise the default is used.
         Set this to null to ignore this setting and configure it through another way.
diff --git a/nixos/modules/services/web-apps/prosody-filer.nix b/nixos/modules/services/web-apps/prosody-filer.nix
index a901a95fd5f9..84953546d8e0 100644
--- a/nixos/modules/services/web-apps/prosody-filer.nix
+++ b/nixos/modules/services/web-apps/prosody-filer.nix
@@ -11,12 +11,12 @@ 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 = ''
+        description = lib.mdDoc ''
           Configuration for Prosody Filer.
-          Refer to <link xlink:href="https://github.com/ThomasLeister/prosody-filer#configure-prosody-filer"/> for details on supported values.
+          Refer to <https://github.com/ThomasLeister/prosody-filer#configure-prosody-filer> for details on supported values.
         '';
 
         type = settingsFormat.type;
@@ -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/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix
index 4b36cc8754c6..4b32f06826e2 100644
--- a/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixos/modules/services/web-apps/restya-board.nix
@@ -25,12 +25,12 @@ in
 
     services.restya-board = {
 
-      enable = mkEnableOption "restya-board";
+      enable = mkEnableOption (lib.mdDoc "restya-board");
 
       dataDir = mkOption {
         type = types.path;
         default = "/var/lib/restya-board";
-        description = ''
+        description = lib.mdDoc ''
           Data of the application.
         '';
       };
@@ -38,7 +38,7 @@ in
       user = mkOption {
         type = types.str;
         default = "restya-board";
-        description = ''
+        description = lib.mdDoc ''
           User account under which the web-application runs.
         '';
       };
@@ -46,7 +46,7 @@ in
       group = mkOption {
         type = types.str;
         default = "nginx";
-        description = ''
+        description = lib.mdDoc ''
           Group account under which the web-application runs.
         '';
       };
@@ -55,7 +55,7 @@ in
         serverName = mkOption {
           type = types.str;
           default = "restya.board";
-          description = ''
+          description = lib.mdDoc ''
             Name of the nginx virtualhost to use.
           '';
         };
@@ -63,15 +63,15 @@ in
         listenHost = mkOption {
           type = types.str;
           default = "localhost";
-          description = ''
+          description = lib.mdDoc ''
             Listen address for the virtualhost to use.
           '';
         };
 
         listenPort = mkOption {
-          type = types.int;
+          type = types.port;
           default = 3000;
-          description = ''
+          description = lib.mdDoc ''
             Listen port for the virtualhost to use.
           '';
         };
@@ -81,7 +81,7 @@ in
         host = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Host of the database. Leave 'null' to use a local PostgreSQL database.
             A local PostgreSQL database is initialized automatically.
           '';
@@ -90,7 +90,7 @@ in
         port = mkOption {
           type = types.nullOr types.int;
           default = 5432;
-          description = ''
+          description = lib.mdDoc ''
             The database's port.
           '';
         };
@@ -98,7 +98,7 @@ in
         name = mkOption {
           type = types.str;
           default = "restya_board";
-          description = ''
+          description = lib.mdDoc ''
             Name of the database. The database must exist.
           '';
         };
@@ -106,7 +106,7 @@ in
         user = mkOption {
           type = types.str;
           default = "restya_board";
-          description = ''
+          description = lib.mdDoc ''
             The database user. The user must exist and have access to
             the specified database.
           '';
@@ -115,7 +115,7 @@ in
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The database user's password. 'null' if no password is set.
           '';
         };
@@ -126,15 +126,15 @@ in
           type = types.nullOr types.str;
           default = null;
           example = "localhost";
-          description = ''
+          description = lib.mdDoc ''
             Hostname to send outgoing mail. Null to use the system MTA.
           '';
         };
 
         port = mkOption {
-          type = types.int;
+          type = types.port;
           default = 25;
-          description = ''
+          description = lib.mdDoc ''
             Port used to connect to SMTP server.
           '';
         };
@@ -142,7 +142,7 @@ in
         login = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             SMTP authentication login used when sending outgoing mail.
           '';
         };
@@ -150,7 +150,7 @@ in
         password = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             SMTP authentication password used when sending outgoing mail.
 
             ATTENTION: The password is stored world-readable in the nix-store!
@@ -161,7 +161,7 @@ in
       timezone = mkOption {
         type = types.lines;
         default = "GMT";
-        description = ''
+        description = lib.mdDoc ''
           Timezone the web-app runs in.
         '';
       };
@@ -263,8 +263,8 @@ in
       serviceConfig.RemainAfterExit = true;
 
       wantedBy = [ "multi-user.target" ];
-      requires = [ "postgresql.service" ];
-      after = [ "network.target" "postgresql.service" ];
+      requires = if cfg.database.host == null then [] else [ "postgresql.service" ];
+      after = [ "network.target" ] ++ (if cfg.database.host == null then [] else [ "postgresql.service" ]);
 
       script = ''
         rm -rf "${runDir}"
@@ -282,7 +282,7 @@ in
           sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', 'restya');/g" "${runDir}/server/php/config.inc.php"
         '' else ''
           sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', '${cfg.database.host}');/g" "${runDir}/server/php/config.inc.php"
-          sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', ${if cfg.database.passwordFile == null then "''" else "'file_get_contents(${cfg.database.passwordFile})'"});/g" "${runDir}/server/php/config.inc.php
+          sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', ${if cfg.database.passwordFile == null then "''" else "'$(cat ${cfg.database.passwordFile})');/g"}" "${runDir}/server/php/config.inc.php"
         ''}
         sed -i "s/^.*'R_DB_PORT'.*$/define('R_DB_PORT', '${toString cfg.database.port}');/g" "${runDir}/server/php/config.inc.php"
         sed -i "s/^.*'R_DB_NAME'.*$/define('R_DB_NAME', '${cfg.database.name}');/g" "${runDir}/server/php/config.inc.php"
@@ -294,7 +294,7 @@ in
         ln -sf "${cfg.dataDir}/client/img" "${runDir}/client/img"
 
         chmod g+w "${runDir}/tmp/cache"
-        chown -R "${cfg.user}"."${cfg.group}" "${runDir}"
+        chown -R "${cfg.user}":"${cfg.group}" "${runDir}"
 
 
         mkdir -m 0750 -p "${cfg.dataDir}"
@@ -302,9 +302,9 @@ in
         mkdir -m 0750 -p "${cfg.dataDir}/client/img"
         cp -r "${pkgs.restya-board}/media/"* "${cfg.dataDir}/media"
         cp -r "${pkgs.restya-board}/client/img/"* "${cfg.dataDir}/client/img"
-        chown "${cfg.user}"."${cfg.group}" "${cfg.dataDir}"
-        chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/media"
-        chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/client/img"
+        chown "${cfg.user}":"${cfg.group}" "${cfg.dataDir}"
+        chown -R "${cfg.user}":"${cfg.group}" "${cfg.dataDir}/media"
+        chown -R "${cfg.user}":"${cfg.group}" "${cfg.dataDir}/client/img"
 
         ${optionalString (cfg.database.host == null) ''
           if ! [ -e "${cfg.dataDir}/.db-initialized" ]; then
diff --git a/nixos/modules/services/web-apps/rss-bridge.nix b/nixos/modules/services/web-apps/rss-bridge.nix
index f2b6d9559823..1a710f4a6a67 100644
--- a/nixos/modules/services/web-apps/rss-bridge.nix
+++ b/nixos/modules/services/web-apps/rss-bridge.nix
@@ -11,12 +11,12 @@ in
 {
   options = {
     services.rss-bridge = {
-      enable = mkEnableOption "rss-bridge";
+      enable = mkEnableOption (lib.mdDoc "rss-bridge");
 
       user = mkOption {
         type = types.str;
         default = "nginx";
-        description = ''
+        description = lib.mdDoc ''
           User account under which both the service and the web-application run.
         '';
       };
@@ -24,7 +24,7 @@ in
       group = mkOption {
         type = types.str;
         default = "nginx";
-        description = ''
+        description = lib.mdDoc ''
           Group under which the web-application run.
         '';
       };
@@ -32,7 +32,7 @@ in
       pool = mkOption {
         type = types.str;
         default = poolName;
-        description = ''
+        description = lib.mdDoc ''
           Name of existing phpfpm pool that is used to run web-application.
           If not specified a pool will be created automatically with
           default values.
@@ -42,16 +42,16 @@ in
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/rss-bridge";
-        description = ''
+        description = lib.mdDoc ''
           Location in which cache directory will be created.
-          You can put <literal>config.ini.php</literal> in here.
+          You can put `config.ini.php` in here.
         '';
       };
 
       virtualHost = mkOption {
         type = types.nullOr types.str;
         default = "rss-bridge";
-        description = ''
+        description = lib.mdDoc ''
           Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
         '';
       };
@@ -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/nixos/modules/services/web-apps/selfoss.nix b/nixos/modules/services/web-apps/selfoss.nix
index 899976ac696c..8debd4904e88 100644
--- a/nixos/modules/services/web-apps/selfoss.nix
+++ b/nixos/modules/services/web-apps/selfoss.nix
@@ -30,12 +30,12 @@ in
   {
     options = {
       services.selfoss = {
-        enable = mkEnableOption "selfoss";
+        enable = mkEnableOption (lib.mdDoc "selfoss");
 
         user = mkOption {
           type = types.str;
           default = "nginx";
-          description = ''
+          description = lib.mdDoc ''
             User account under which both the service and the web-application run.
           '';
         };
@@ -43,7 +43,7 @@ in
         pool = mkOption {
           type = types.str;
           default = "${poolName}";
-          description = ''
+          description = lib.mdDoc ''
             Name of existing phpfpm pool that is used to run web-application.
             If not specified a pool will be created automatically with
             default values.
@@ -54,7 +54,7 @@ in
         type = mkOption {
           type = types.enum ["pgsql" "mysql" "sqlite"];
           default = "sqlite";
-          description = ''
+          description = lib.mdDoc ''
             Database to store feeds. Supported are sqlite, pgsql and mysql.
           '';
         };
@@ -62,7 +62,7 @@ in
         host = mkOption {
           type = types.str;
           default = "localhost";
-          description = ''
+          description = lib.mdDoc ''
             Host of the database (has no effect if type is "sqlite").
           '';
         };
@@ -70,7 +70,7 @@ in
         name = mkOption {
           type = types.str;
           default = "tt_rss";
-          description = ''
+          description = lib.mdDoc ''
             Name of the existing database (has no effect if type is "sqlite").
           '';
         };
@@ -78,7 +78,7 @@ in
         user = mkOption {
           type = types.str;
           default = "tt_rss";
-          description = ''
+          description = lib.mdDoc ''
             The database user. The user must exist and has access to
             the specified database (has no effect if type is "sqlite").
           '';
@@ -87,7 +87,7 @@ in
         password = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The database user's password (has no effect if type is "sqlite").
           '';
         };
@@ -95,7 +95,7 @@ in
         port = mkOption {
           type = types.nullOr types.int;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The database's port. If not set, the default ports will be
             provided (5432 and 3306 for pgsql and mysql respectively)
             (has no effect if type is "sqlite").
@@ -105,7 +105,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration added to config.ini
         '';
       };
diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix
index bb2fc684e83b..f0505e052e1c 100644
--- a/nixos/modules/services/web-apps/shiori.nix
+++ b/nixos/modules/services/web-apps/shiori.nix
@@ -6,19 +6,19 @@ let
 in {
   options = {
     services.shiori = {
-      enable = mkEnableOption "Shiori simple bookmarks manager";
+      enable = mkEnableOption (lib.mdDoc "Shiori simple bookmarks manager");
 
       package = mkOption {
         type = types.package;
         default = pkgs.shiori;
         defaultText = literalExpression "pkgs.shiori";
-        description = "The Shiori package to use.";
+        description = lib.mdDoc "The Shiori package to use.";
       };
 
       address = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           The IP address on which Shiori will listen.
           If empty, listens on all interfaces.
         '';
@@ -27,7 +27,7 @@ in {
       port = mkOption {
         type = types.port;
         default = 8080;
-        description = "The port of the Shiori web application";
+        description = lib.mdDoc "The port of the Shiori web application";
       };
     };
   };
@@ -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/nixos/modules/services/web-apps/snipe-it.nix b/nixos/modules/services/web-apps/snipe-it.nix
new file mode 100644
index 000000000000..314a69a73a87
--- /dev/null
+++ b/nixos/modules/services/web-apps/snipe-it.nix
@@ -0,0 +1,509 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.snipe-it;
+  snipe-it = pkgs.snipe-it.override {
+    dataDir = cfg.dataDir;
+  };
+  db = cfg.database;
+  mail = cfg.mail;
+
+  user = cfg.user;
+  group = cfg.group;
+
+  tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
+
+  # shell script for local administration
+  artisan = pkgs.writeScriptBin "snipe-it" ''
+    #! ${pkgs.runtimeShell}
+    cd ${snipe-it}
+    sudo=exec
+    if [[ "$USER" != ${user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${user}'
+    fi
+    $sudo ${pkgs.php}/bin/php artisan $*
+  '';
+in {
+  options.services.snipe-it = {
+
+    enable = mkEnableOption (lib.mdDoc "A free open source IT asset/license management system");
+
+    user = mkOption {
+      default = "snipeit";
+      description = lib.mdDoc "User snipe-it runs as.";
+      type = types.str;
+    };
+
+    group = mkOption {
+      default = "snipeit";
+      description = lib.mdDoc "Group snipe-it 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 `head -c 32 /dev/urandom | base64`.
+      '';
+      example = "/run/keys/snipe-it/appkey";
+      type = types.path;
+    };
+
+    hostName = lib.mkOption {
+      type = lib.types.str;
+      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.
+      '';
+    };
+
+    appURL = mkOption {
+      description = lib.mdDoc ''
+        The root URL that you want to host Snipe-IT on. All URLs in Snipe-IT 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: `snipe-it snipe-it:update-url https://old.example.com https://new.example.com`
+      '';
+      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 "snipe-it data directory";
+      default = "/var/lib/snipe-it";
+      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 = "snipeit";
+        description = lib.mdDoc "Database name.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = user;
+        defaultText = literalExpression "user";
+        description = lib.mdDoc "Database username.";
+      };
+      passwordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "/run/keys/snipe-it/dbpassword";
+        description = lib.mdDoc ''
+          A file containing the password corresponding to
+          {option}`database.user`.
+        '';
+      };
+      createLocally = mkOption {
+        type = types.bool;
+        default = false;
+        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.";
+      };
+      encryption = mkOption {
+        type = with types; nullOr (enum [ "tls" "ssl" ]);
+        default = null;
+        description = lib.mdDoc "SMTP encryption mechanism to use.";
+      };
+      user = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        example = "snipeit";
+        description = lib.mdDoc "Mail username.";
+      };
+      passwordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "/run/keys/snipe-it/mailpassword";
+        description = lib.mdDoc ''
+          A file containing the password corresponding to
+          {option}`mail.user`.
+        '';
+      };
+      backupNotificationAddress = mkOption {
+        type = types.str;
+        default = "backup@example.com";
+        description = lib.mdDoc "Email Address to send Backup Notifications to.";
+      };
+      from = {
+        name = mkOption {
+          type = types.str;
+          default = "Snipe-IT Asset Management";
+          description = lib.mdDoc "Mail \"from\" name.";
+        };
+        address = mkOption {
+          type = types.str;
+          default = "mail@example.com";
+          description = lib.mdDoc "Mail \"from\" address.";
+        };
+      };
+      replyTo = {
+        name = mkOption {
+          type = types.str;
+          default = "Snipe-IT Asset Management";
+          description = lib.mdDoc "Mail \"reply-to\" name.";
+        };
+        address = mkOption {
+          type = types.str;
+          default = "mail@example.com";
+          description = lib.mdDoc "Mail \"reply-to\" address.";
+        };
+      };
+    };
+
+    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 snipe-it PHP pool. See the documentation on `php-fpm.conf`
+        for details on configuration directives.
+      '';
+    };
+
+    nginx = mkOption {
+      type = types.submodule (
+        recursiveUpdate
+          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
+      );
+      default = {};
+      example = literalExpression ''
+        {
+          serverAliases = [
+            "snipe-it.''${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 (oneOf [ str path ]);
+                    description = lib.mdDoc ''
+                      The path to a file containing the value the
+                      option should be set to in the final
+                      configuration file.
+                    '';
+                  };
+                };
+              })));
+      default = {};
+      example = literalExpression ''
+        {
+          ALLOWED_IFRAME_HOSTS = "https://example.com";
+          WKHTMLTOPDF = "''${pkgs.wkhtmltopdf}/bin/wkhtmltopdf";
+          AUTH_METHOD = "oidc";
+          OIDC_NAME = "MyLogin";
+          OIDC_DISPLAY_NAME_CLAIMS = "name";
+          OIDC_CLIENT_ID = "snipe-it";
+          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 ''
+        Snipe-IT configuration options to set in the
+        {file}`.env` file.
+        Refer to <https://snipe-it.readme.io/docs/configuration>
+        for details on supported values.
+
+        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 to get a better picture of
+        this: in the resulting {file}`.env` file, the
+        `OIDC_CLIENT_SECRET` key will be set to the
+        contents of the {file}`/run/keys/oidc_secret`
+        file.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = db.createLocally -> db.user == user;
+        message = "services.snipe-it.database.user must be set to ${user} if services.snipe-it.database.createLocally is set true.";
+      }
+      { assertion = db.createLocally -> db.passwordFile == null;
+        message = "services.snipe-it.database.passwordFile cannot be specified if services.snipe-it.database.createLocally is set to true.";
+      }
+    ];
+
+    environment.systemPackages = [ artisan ];
+
+    services.snipe-it.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;
+      DB_PASSWORD._secret = db.passwordFile;
+      MAIL_DRIVER = mail.driver;
+      MAIL_FROM_NAME = mail.from.name;
+      MAIL_FROM_ADDR = mail.from.address;
+      MAIL_REPLYTO_NAME = mail.from.name;
+      MAIL_REPLYTO_ADDR = mail.from.address;
+      MAIL_BACKUP_NOTIFICATION_ADDRESS = mail.backupNotificationAddress;
+      MAIL_HOST = mail.host;
+      MAIL_PORT = mail.port;
+      MAIL_USERNAME = mail.user;
+      MAIL_ENCRYPTION = mail.encryption;
+      MAIL_PASSWORD._secret = mail.passwordFile;
+      APP_SERVICES_CACHE = "/run/snipe-it/cache/services.php";
+      APP_PACKAGES_CACHE = "/run/snipe-it/cache/packages.php";
+      APP_CONFIG_CACHE = "/run/snipe-it/cache/config.php";
+      APP_ROUTES_CACHE = "/run/snipe-it/cache/routes-v7.php";
+      APP_EVENTS_CACHE = "/run/snipe-it/cache/events.php";
+      SESSION_SECURE_COOKIE = tlsEnabled;
+    };
+
+    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.snipe-it = {
+      inherit user group;
+      phpPackage = pkgs.php81;
+      phpOptions = ''
+        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;
+      virtualHosts."${cfg.hostName}" = mkMerge [ cfg.nginx {
+        root = mkForce "${snipe-it}/public";
+        extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;";
+        locations = {
+          "/" = {
+            index = "index.php";
+            extraConfig = ''try_files $uri $uri/ /index.php?$query_string;'';
+          };
+          "~ \.php$" = {
+            extraConfig = ''
+              try_files $uri $uri/ /index.php?$query_string;
+              include ${config.services.nginx.package}/conf/fastcgi_params;
+              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+              fastcgi_param REDIRECT_STATUS 200;
+              fastcgi_pass unix:${config.services.phpfpm.pools."snipe-it".socket};
+              ${optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"}
+            '';
+          };
+          "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
+            extraConfig = "expires 365d;";
+          };
+        };
+      }];
+    };
+
+    systemd.services.snipe-it-setup = {
+      description = "Preparation tasks for snipe-it";
+      before = [ "phpfpm-snipe-it.service" ];
+      after = optional db.createLocally "mysql.service";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = user;
+        WorkingDirectory = snipe-it;
+        RuntimeDirectory = "snipe-it/cache";
+        RuntimeDirectoryMode = "0700";
+      };
+      path = [ pkgs.replace-secret ];
+      script =
+        let
+          isSecret  = v: isAttrs v && v ? _secret && (isString v._secret || builtins.isPath v._secret);
+          snipeITEnvVars = 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
+                  if (isString v._secret) then
+                    hashString "sha256" v._secret
+                  else
+                    hashString "sha256" (builtins.readFile 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 [
+              (
+                if (isString file) then
+                  builtins.hashString "sha256" file
+                else
+                  builtins.hashString "sha256" (builtins.readFile file)
+              )
+              file
+              "${cfg.dataDir}/.env"
+            ]}
+          '';
+          secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
+          filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ {} null ])) cfg.config;
+          snipeITEnv = pkgs.writeText "snipeIT.env" (snipeITEnvVars filteredConfig);
+        in ''
+          # error handling
+          set -euo pipefail
+
+          # set permissions
+          umask 077
+
+          # create .env file
+          install -T -m 0600 -o ${user} ${snipeITEnv} "${cfg.dataDir}/.env"
+
+          # replace secrets
+          ${secretReplacements}
+
+          # prepend `base64:` if it does not exist in APP_KEY
+          if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
+              sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
+          fi
+
+          # purge cache
+          rm "${cfg.dataDir}"/bootstrap/cache/*.php || true
+
+          # 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"
+          [ ! -e "$invalid_barcode_location" ] \
+              && cp ${snipe-it}/share/snipe-it/invalid_barcode.gif "$invalid_barcode_location"
+        '';
+    };
+
+    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}/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 = {
+      users = mkIf (user == "snipeit") {
+        snipeit = {
+          inherit group;
+          isSystemUser = true;
+        };
+        "${config.services.nginx.user}".extraGroups = [ group ];
+      };
+      groups = mkIf (group == "snipeit") {
+        snipeit = {};
+      };
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ yayayayaka ];
+}
diff --git a/nixos/modules/services/web-apps/sogo.nix b/nixos/modules/services/web-apps/sogo.nix
index 4610bb96cb5e..5e5d9472829d 100644
--- a/nixos/modules/services/web-apps/sogo.nix
+++ b/nixos/modules/services/web-apps/sogo.nix
@@ -18,38 +18,38 @@
 
 in {
   options.services.sogo = with types; {
-    enable = mkEnableOption "SOGo groupware";
+    enable = mkEnableOption (lib.mdDoc "SOGo groupware");
 
     vhostName = mkOption {
-      description = "Name of the nginx vhost";
+      description = lib.mdDoc "Name of the nginx vhost";
       type = str;
       default = "sogo";
     };
 
     timezone = mkOption {
-      description = "Timezone of your SOGo instance";
+      description = lib.mdDoc "Timezone of your SOGo instance";
       type = str;
       example = "America/Montreal";
     };
 
     language = mkOption {
-      description = "Language of SOGo";
+      description = lib.mdDoc "Language of SOGo";
       type = str;
       default = "English";
     };
 
     ealarmsCredFile = mkOption {
-      description = "Optional path to a credentials file for email alarms";
+      description = lib.mdDoc "Optional path to a credentials file for email alarms";
       type = nullOr str;
       default = null;
     };
 
     configReplaces = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         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;
@@ -60,7 +60,7 @@ in {
     };
 
     extraConfig = mkOption {
-      description = "Extra sogo.conf configuration lines";
+      description = lib.mdDoc "Extra sogo.conf configuration lines";
       type = lines;
       default = "";
     };
diff --git a/nixos/modules/services/web-apps/timetagger.nix b/nixos/modules/services/web-apps/timetagger.nix
deleted file mode 100644
index 373f4fcd52f8..000000000000
--- a/nixos/modules/services/web-apps/timetagger.nix
+++ /dev/null
@@ -1,80 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  inherit (lib) mkEnableOption mkIf mkOption types literalExpression;
-
-  cfg = config.services.timetagger;
-in {
-
-  options = {
-    services.timetagger = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Tag your time, get the insight
-
-          <note><para>
-            This app does not do authentication.
-            You must setup authentication yourself or run it in an environment where
-            only allowed users have access.
-          </para></note>
-        '';
-      };
-
-      bindAddr = mkOption {
-        description = "Address to bind to.";
-        type = types.str;
-        default = "127.0.0.1";
-      };
-
-      port = mkOption {
-        description = "Port to bind to.";
-        type = types.port;
-        default = 8080;
-      };
-
-      package = mkOption {
-        description = ''
-          Use own package for starting timetagger web application.
-
-          The ${literalExpression ''pkgs.timetagger''} package only provides a
-          "run.py" script for the actual package
-          ${literalExpression ''pkgs.python3Packages.timetagger''}.
-
-          If you want to provide a "run.py" script for starting timetagger
-          yourself, you can do so with this option.
-          If you do so, the 'bindAddr' and 'port' options are ignored.
-        '';
-
-        default = pkgs.timetagger.override { addr = cfg.bindAddr; port = cfg.port; };
-        defaultText = literalExpression ''
-          pkgs.timetagger.override {
-            addr = ${cfg.bindAddr};
-            port = ${cfg.port};
-          };
-        '';
-        type = types.package;
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.timetagger = {
-      description = "Timetagger service";
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        User = "timetagger";
-        Group = "timetagger";
-        StateDirectory = "timetagger";
-
-        ExecStart = "${cfg.package}/bin/timetagger";
-
-        Restart = "on-failure";
-        RestartSec = 1;
-      };
-    };
-  };
-}
-
diff --git a/nixos/modules/services/web-apps/trilium.nix b/nixos/modules/services/web-apps/trilium.nix
index 35383c992fe8..a91d64f620b6 100644
--- a/nixos/modules/services/web-apps/trilium.nix
+++ b/nixos/modules/services/web-apps/trilium.nix
@@ -10,6 +10,7 @@ let
     # Disable automatically generating desktop icon
     noDesktopIcon=true
     noBackup=${lib.boolToString cfg.noBackup}
+    noAuthentication=${lib.boolToString cfg.noAuthentication}
 
     [Network]
     # host setting is relevant only for web deployments - set the host on which the server will listen
@@ -23,12 +24,12 @@ in
 {
 
   options.services.trilium-server = with lib; {
-    enable = mkEnableOption "trilium-server";
+    enable = mkEnableOption (lib.mdDoc "trilium-server");
 
     dataDir = mkOption {
       type = types.str;
       default = "/var/lib/trilium";
-      description = ''
+      description = lib.mdDoc ''
         The directory storing the notes database and the configuration.
       '';
     };
@@ -36,7 +37,7 @@ in
     instanceName = mkOption {
       type = types.str;
       default = "Trilium";
-      description = ''
+      description = lib.mdDoc ''
         Instance name used to distinguish between different instances
       '';
     };
@@ -44,30 +45,38 @@ in
     noBackup = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Disable periodic database backups.
       '';
     };
 
+    noAuthentication = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        If set to true, no password is required to access the web frontend.
+      '';
+    };
+
     host = mkOption {
       type = types.str;
       default = "127.0.0.1";
-      description = ''
+      description = lib.mdDoc ''
         The host address to bind to (defaults to localhost).
       '';
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 8080;
-      description = ''
+      description = lib.mdDoc ''
         The port number to bind to.
       '';
     };
 
     nginx = mkOption {
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Configuration for nginx reverse proxy.
       '';
 
@@ -76,14 +85,14 @@ in
           enable = mkOption {
             type = types.bool;
             default = false;
-            description = ''
+            description = lib.mdDoc ''
               Configure the nginx reverse proxy settings.
             '';
           };
 
           hostName = mkOption {
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               The hostname use to setup the virtualhost configuration
             '';
           };
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 9aa38ab25c9a..6f494fae4cc1 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -121,12 +121,12 @@ let
 
     services.tt-rss = {
 
-      enable = mkEnableOption "tt-rss";
+      enable = mkEnableOption (lib.mdDoc "tt-rss");
 
       root = mkOption {
         type = types.path;
         default = "/var/lib/tt-rss";
-        description = ''
+        description = lib.mdDoc ''
           Root of the application.
         '';
       };
@@ -134,7 +134,7 @@ let
       user = mkOption {
         type = types.str;
         default = "tt_rss";
-        description = ''
+        description = lib.mdDoc ''
           User account under which both the update daemon and the web-application run.
         '';
       };
@@ -142,7 +142,7 @@ let
       pool = mkOption {
         type = types.str;
         default = "${poolName}";
-        description = ''
+        description = lib.mdDoc ''
           Name of existing phpfpm pool that is used to run web-application.
           If not specified a pool will be created automatically with
           default values.
@@ -152,7 +152,7 @@ let
       virtualHost = mkOption {
         type = types.nullOr types.str;
         default = "tt-rss";
-        description = ''
+        description = lib.mdDoc ''
           Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
         '';
       };
@@ -161,7 +161,7 @@ let
         type = mkOption {
           type = types.enum ["pgsql" "mysql"];
           default = "pgsql";
-          description = ''
+          description = lib.mdDoc ''
             Database to store feeds. Supported are pgsql and mysql.
           '';
         };
@@ -169,7 +169,7 @@ let
         host = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Host of the database. Leave null to use Unix domain socket.
           '';
         };
@@ -177,7 +177,7 @@ let
         name = mkOption {
           type = types.str;
           default = "tt_rss";
-          description = ''
+          description = lib.mdDoc ''
             Name of the existing database.
           '';
         };
@@ -185,7 +185,7 @@ let
         user = mkOption {
           type = types.str;
           default = "tt_rss";
-          description = ''
+          description = lib.mdDoc ''
             The database user. The user must exist and has access to
             the specified database.
           '';
@@ -194,7 +194,7 @@ let
         password = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The database user's password.
           '';
         };
@@ -202,15 +202,15 @@ let
         passwordFile = mkOption {
           type = types.nullOr types.str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The database user's password.
           '';
         };
 
         port = mkOption {
-          type = types.nullOr types.int;
+          type = types.nullOr types.port;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             The database's port. If not set, the default ports will be provided (5432
             and 3306 for pgsql and mysql respectively).
           '';
@@ -219,7 +219,7 @@ let
         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.";
         };
       };
 
@@ -227,7 +227,7 @@ let
         autoCreate = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Allow authentication modules to auto-create users in tt-rss internal
             database when authenticated successfully.
           '';
@@ -236,7 +236,7 @@ let
         autoLogin = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Automatically login user on remote or other kind of externally supplied
             authentication, otherwise redirect to login form as normal.
             If set to true, users won't be able to set application language
@@ -249,7 +249,7 @@ let
         hub = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             URL to a PubSubHubbub-compatible hub server. If defined, "Published
             articles" generated feed would automatically become PUSH-enabled.
           '';
@@ -258,7 +258,7 @@ let
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss
             won't try to subscribe to PUSH feed updates.
           '';
@@ -269,7 +269,7 @@ let
         server = mkOption {
           type = types.str;
           default = "localhost:9312";
-          description = ''
+          description = lib.mdDoc ''
             Hostname:port combination for the Sphinx server.
           '';
         };
@@ -277,7 +277,7 @@ let
         index = mkOption {
           type = types.listOf types.str;
           default = ["ttrss" "delta"];
-          description = ''
+          description = lib.mdDoc ''
             Index names in Sphinx configuration. Example configuration
             files are available on tt-rss wiki.
           '';
@@ -288,7 +288,7 @@ let
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Allow users to register themselves. Please be aware that allowing
             random people to access your tt-rss installation is a security risk
             and potentially might lead to data loss or server exploit. Disabled
@@ -299,7 +299,7 @@ let
         notifyAddress = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Email address to send new user notifications to.
           '';
         };
@@ -307,7 +307,7 @@ let
         maxUsers = mkOption {
           type = types.int;
           default = 0;
-          description = ''
+          description = lib.mdDoc ''
             Maximum amount of users which will be allowed to register on this
             system. 0 - no limit.
           '';
@@ -319,7 +319,7 @@ let
           type = types.str;
           default = "";
           example = "localhost:25";
-          description = ''
+          description = lib.mdDoc ''
             Hostname:port combination to send outgoing mail. Blank - use system
             MTA.
           '';
@@ -328,7 +328,7 @@ let
         login = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             SMTP authentication login used when sending outgoing mail.
           '';
         };
@@ -336,7 +336,7 @@ let
         password = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             SMTP authentication password used when sending outgoing mail.
           '';
         };
@@ -344,7 +344,7 @@ let
         security = mkOption {
           type = types.enum ["" "ssl" "tls"];
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Used to select a secure SMTP connection. Allowed values: ssl, tls,
             or empty.
           '';
@@ -353,7 +353,7 @@ let
         fromName = mkOption {
           type = types.str;
           default = "Tiny Tiny RSS";
-          description = ''
+          description = lib.mdDoc ''
             Name for sending outgoing mail. This applies to password reset
             notifications, digest emails and any other mail.
           '';
@@ -362,7 +362,7 @@ let
         fromAddress = mkOption {
           type = types.str;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Address for sending outgoing mail. This applies to password reset
             notifications, digest emails and any other mail.
           '';
@@ -371,7 +371,7 @@ let
         digestSubject = mkOption {
           type = types.str;
           default = "[tt-rss] New headlines for last 24 hours";
-          description = ''
+          description = lib.mdDoc ''
             Subject line for email digests.
           '';
         };
@@ -380,7 +380,7 @@ let
       sessionCookieLifetime = mkOption {
         type = types.int;
         default = 86400;
-        description = ''
+        description = lib.mdDoc ''
           Default lifetime of a session (e.g. login) cookie. In seconds,
           0 means cookie will be deleted when browser closes.
         '';
@@ -388,7 +388,7 @@ let
 
       selfUrlPath = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Full URL of your tt-rss installation. This should be set to the
           location of tt-rss directory, e.g. http://example.org/tt-rss/
           You need to set this option correctly otherwise several features
@@ -400,7 +400,7 @@ let
       feedCryptKey = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Key used for encryption of passwords for password-protected feeds
           in the database. A string of 24 random characters. If left blank, encryption
           is not used. Requires mcrypt functions.
@@ -413,7 +413,7 @@ let
         type = types.bool;
         default = false;
 
-        description = ''
+        description = lib.mdDoc ''
           Operate in single user mode, disables all functionality related to
           multiple users and authentication. Enabling this assumes you have
           your tt-rss directory protected by other means (e.g. http auth).
@@ -423,7 +423,7 @@ let
       simpleUpdateMode = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enables fallback update mode where tt-rss tries to update feeds in
           background while tt-rss is open in your browser.
           If you don't have a lot of feeds and don't want to or can't run
@@ -437,7 +437,7 @@ let
       forceArticlePurge = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           When this option is not 0, users ability to control feed purging
           intervals is disabled and all articles (which are not starred)
           older than this amount of days are purged.
@@ -447,7 +447,7 @@ let
       enableGZipOutput = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Selectively gzip output to improve wire performance. This requires
           PHP Zlib extension on the server.
           Enabling this can break tt-rss in several httpd/php configurations,
@@ -459,7 +459,7 @@ let
       plugins = mkOption {
         type = types.listOf types.str;
         default = ["auth_internal" "note"];
-        description = ''
+        description = lib.mdDoc ''
           List of plugins to load automatically for all users.
           System plugins have to be specified here. Please enable at least one
           authentication plugin here (auth_*).
@@ -473,27 +473,27 @@ let
       pluginPackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of plugins to install. The list elements are expected to
           be derivations. All elements in this derivation are automatically
-          copied to the <literal>plugins.local</literal> directory.
+          copied to the `plugins.local` directory.
         '';
       };
 
       themePackages = mkOption {
         type = types.listOf types.package;
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           List of themes to install. The list elements are expected to
           be derivations. All elements in this derivation are automatically
-          copied to the <literal>themes.local</literal> directory.
+          copied to the `themes.local` directory.
         '';
       };
 
       logDestination = mkOption {
         type = types.enum ["" "sql" "syslog"];
         default = "sql";
-        description = ''
+        description = lib.mdDoc ''
           Log destination to use. Possible values: sql (uses internal logging
           you can read in Preferences -> System), syslog - logs to system log.
           Setting this to blank uses PHP logging (usually to http server
@@ -504,8 +504,8 @@ let
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Additional lines to append to <literal>config.php</literal>.
+        description = lib.mdDoc ''
+          Additional lines to append to `config.php`.
         '';
       };
     };
@@ -534,6 +534,7 @@ let
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
       ${poolName} = {
         inherit (cfg) user;
+        phpPackage = pkgs.php80;
         settings = mapAttrs (name: mkDefault) {
           "listen.owner" = "nginx";
           "listen.group" = "nginx";
diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix
index 7575e96ca815..c3552200d4e5 100644
--- a/nixos/modules/services/web-apps/vikunja.nix
+++ b/nixos/modules/services/web-apps/vikunja.nix
@@ -10,23 +10,23 @@ 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;
       defaultText = literalExpression "pkgs.vikunja-api";
-      description = "vikunja-api derivation to use.";
+      description = lib.mdDoc "vikunja-api derivation to use.";
     };
     package-frontend = mkOption {
       default = pkgs.vikunja-frontend;
       type = types.package;
       defaultText = literalExpression "pkgs.vikunja-frontend";
-      description = "vikunja-frontend derivation to use.";
+      description = lib.mdDoc "vikunja-frontend derivation to use.";
     };
     environmentFiles = mkOption {
       type = types.listOf types.path;
       default = [ ];
-      description = ''
+      description = lib.mdDoc ''
         List of environment files set in the vikunja systemd service.
         For example passwords should be set in one of these files.
       '';
@@ -35,34 +35,34 @@ in {
       type = types.bool;
       default = config.services.nginx.enable;
       defaultText = literalExpression "config.services.nginx.enable";
-      description = ''
+      description = lib.mdDoc ''
         Whether to setup NGINX.
         Further nginx configuration can be done by changing
-        <option>services.nginx.virtualHosts.&lt;frontendHostname&gt;</option>.
+        {option}`services.nginx.virtualHosts.<frontendHostname>`.
         This does not enable TLS or ACME by default. To enable this, set the
-        <option>services.nginx.virtualHosts.&lt;frontendHostname&gt;.enableACME</option> to
-        <literal>true</literal> and if appropriate do the same for
-        <option>services.nginx.virtualHosts.&lt;frontendHostname&gt;.forceSSL</option>.
+        {option}`services.nginx.virtualHosts.<frontendHostname>.enableACME` to
+        `true` and if appropriate do the same for
+        {option}`services.nginx.virtualHosts.<frontendHostname>.forceSSL`.
       '';
     };
     frontendScheme = mkOption {
       type = types.enum [ "http" "https" ];
-      description = ''
+      description = lib.mdDoc ''
         Whether the site is available via http or https.
         This does not configure https or ACME in nginx!
       '';
     };
     frontendHostname = mkOption {
       type = types.str;
-      description = "The Hostname under which the frontend is running.";
+      description = lib.mdDoc "The Hostname under which the frontend is running.";
     };
 
     settings = mkOption {
       type = format.type;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Vikunja configuration. Refer to
-        <link xlink:href="https://vikunja.io/docs/config-options/"/>
+        <https://vikunja.io/docs/config-options/>
         for details on supported values.
         '';
     };
@@ -71,27 +71,27 @@ in {
         type = types.enum [ "sqlite" "mysql" "postgres" ];
         example = "postgres";
         default = "sqlite";
-        description = "Database engine to use.";
+        description = lib.mdDoc "Database engine to use.";
       };
       host = mkOption {
         type = types.str;
         default = "localhost";
-        description = "Database host address. Can also be a socket.";
+        description = lib.mdDoc "Database host address. Can also be a socket.";
       };
       user = mkOption {
         type = types.str;
         default = "vikunja";
-        description = "Database user.";
+        description = lib.mdDoc "Database user.";
       };
       database = mkOption {
         type = types.str;
         default = "vikunja";
-        description = "Database name.";
+        description = lib.mdDoc "Database name.";
       };
       path = mkOption {
         type = types.str;
         default = "/var/lib/vikunja/vikunja.db";
-        description = "Path to the sqlite3 database file.";
+        description = lib.mdDoc "Path to the sqlite3 database file.";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/virtlyst.nix b/nixos/modules/services/web-apps/virtlyst.nix
deleted file mode 100644
index 37bdbb0e3b42..000000000000
--- a/nixos/modules/services/web-apps/virtlyst.nix
+++ /dev/null
@@ -1,73 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.virtlyst;
-  stateDir = "/var/lib/virtlyst";
-
-  ini = pkgs.writeText "virtlyst-config.ini" ''
-    [wsgi]
-    master = true
-    threads = auto
-    http-socket = ${cfg.httpSocket}
-    application = ${pkgs.virtlyst}/lib/libVirtlyst.so
-    chdir2 = ${stateDir}
-    static-map = /static=${pkgs.virtlyst}/root/static
-
-    [Cutelyst]
-    production = true
-    DatabasePath = virtlyst.sqlite
-    TemplatePath = ${pkgs.virtlyst}/root/src
-
-    [Rules]
-    cutelyst.* = true
-    virtlyst.* = true
-  '';
-
-in
-
-{
-
-  options.services.virtlyst = {
-    enable = mkEnableOption "Virtlyst libvirt web interface";
-
-    adminPassword = mkOption {
-      type = types.str;
-      description = ''
-        Initial admin password with which the database will be seeded.
-      '';
-    };
-
-    httpSocket = mkOption {
-      type = types.str;
-      default = "localhost:3000";
-      description = ''
-        IP and/or port to which to bind the http socket.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.virtlyst = {
-      home = stateDir;
-      createHome = true;
-      group = mkIf config.virtualisation.libvirtd.enable "libvirtd";
-      isSystemUser = true;
-    };
-
-    systemd.services.virtlyst = {
-      wantedBy = [ "multi-user.target" ];
-      environment = {
-        VIRTLYST_ADMIN_PASSWORD = cfg.adminPassword;
-      };
-      serviceConfig = {
-        ExecStart = "${pkgs.cutelyst}/bin/cutelyst-wsgi2 --ini ${ini}";
-        User = "virtlyst";
-        WorkingDirectory = stateDir;
-      };
-    };
-  };
-
-}
diff --git a/nixos/modules/services/web-apps/whitebophir.nix b/nixos/modules/services/web-apps/whitebophir.nix
index f9db6fe379b0..b673a7c1179e 100644
--- a/nixos/modules/services/web-apps/whitebophir.nix
+++ b/nixos/modules/services/web-apps/whitebophir.nix
@@ -7,25 +7,25 @@ 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;
         defaultText = literalExpression "pkgs.whitebophir";
         type = types.package;
-        description = "Whitebophir package to use.";
+        description = lib.mdDoc "Whitebophir package to use.";
       };
 
       listenAddress = mkOption {
         type = types.str;
         default = "0.0.0.0";
-        description = "Address to listen on (use 0.0.0.0 to allow access from any address).";
+        description = lib.mdDoc "Address to listen on (use 0.0.0.0 to allow access from any address).";
       };
 
       port = mkOption {
         type = types.port;
         default = 5001;
-        description = "Port to bind to.";
+        description = lib.mdDoc "Port to bind to.";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/wiki-js.nix b/nixos/modules/services/web-apps/wiki-js.nix
index 1a6259dffeef..b6e5b4594f1d 100644
--- a/nixos/modules/services/web-apps/wiki-js.nix
+++ b/nixos/modules/services/web-apps/wiki-js.nix
@@ -10,22 +10,22 @@ 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 = ''
-        Environment fiel to inject e.g. secrets into the configuration.
+      description = lib.mdDoc ''
+        Environment file to inject e.g. secrets into the configuration.
       '';
     };
 
     stateDirectoryName = mkOption {
       default = "wiki-js";
       type = types.str;
-      description = ''
-        Name of the directory in <filename>/var/lib</filename>.
+      description = lib.mdDoc ''
+        Name of the directory in {file}`/var/lib`.
       '';
     };
 
@@ -37,7 +37,7 @@ in {
           port = mkOption {
             type = types.port;
             default = 3000;
-            description = ''
+            description = lib.mdDoc ''
               TCP port the process should listen to.
             '';
           };
@@ -45,7 +45,7 @@ in {
           bindIP = mkOption {
             default = "0.0.0.0";
             type = types.str;
-            description = ''
+            description = lib.mdDoc ''
               IPs the service should listen to.
             '';
           };
@@ -54,24 +54,24 @@ 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.
               '';
             };
             host = mkOption {
               type = types.str;
               example = "/run/postgresql";
-              description = ''
+              description = lib.mdDoc ''
                 Hostname or socket-path to connect to.
               '';
             };
             db = mkOption {
               default = "wiki";
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Name of the database to use.
               '';
             };
@@ -80,31 +80,28 @@ in {
           logLevel = mkOption {
             default = "info";
             type = types.enum [ "error" "warn" "info" "verbose" "debug" "silly" ];
-            description = ''
+            description = lib.mdDoc ''
               Define how much detail is supposed to be logged at runtime.
             '';
           };
 
-          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.
       '';
     };
   };
@@ -130,7 +127,7 @@ in {
         WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
         DynamicUser = true;
         PrivateTmp = true;
-        ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.wiki-js}/server";
+        ExecStart = "${pkgs.nodejs-16_x}/bin/node ${pkgs.wiki-js}/server";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index 59471a739cbb..43a6d7e75dc6 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/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,9 +31,10 @@ 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)
+      # copy additional plugin(s), theme(s) and language(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}
+      ${concatMapStringsSep "\n" (language: "cp -r ${language} $out/share/wordpress/wp-content/languages/") cfg.languages}
     '';
   };
 
@@ -82,24 +84,36 @@ let
           type = types.package;
           default = pkgs.wordpress;
           defaultText = literalExpression "pkgs.wordpress";
-          description = "Which WordPress package to use.";
+          description = lib.mdDoc "Which WordPress package to use.";
         };
 
         uploadsDir = mkOption {
           type = types.path;
           default = "/var/lib/wordpress/${name}/uploads";
-          description = ''
+          description = lib.mdDoc ''
             This directory is used for uploads of pictures. The directory passed here is automatically
             created and permissions adjusted as required.
           '';
         };
 
+        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 = ''
+          description = lib.mdDoc ''
             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>
+
+            ::: {.note}
+            These plugins need to be packaged before use, see example.
+            :::
           '';
           example = literalExpression ''
             let
@@ -124,9 +138,12 @@ let
         themes = mkOption {
           type = types.listOf types.path;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             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>
+
+            ::: {.note}
+            These themes need to be packaged before use, see example.
+            :::
           '';
           example = literalExpression ''
             let
@@ -148,51 +165,77 @@ let
           '';
         };
 
+        languages = mkOption {
+          type = types.listOf types.path;
+          default = [];
+          description = lib.mdDoc ''
+            List of path(s) to respective language(s) which are copied from the 'languages' directory.
+          '';
+          example = literalExpression ''
+            [(
+              # 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://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=";
+                };
+                installPhase = "mkdir -p $out; cp -r ./wp-content/languages/* $out/";
+              };
+            )];
+          '';
+        };
+
         database = {
           host = mkOption {
             type = types.str;
             default = "localhost";
-            description = "Database host address.";
+            description = lib.mdDoc "Database host address.";
           };
 
           port = mkOption {
             type = types.port;
             default = 3306;
-            description = "Database host port.";
+            description = lib.mdDoc "Database host port.";
           };
 
           name = mkOption {
             type = types.str;
             default = "wordpress";
-            description = "Database name.";
+            description = lib.mdDoc "Database name.";
           };
 
           user = mkOption {
             type = types.str;
             default = "wordpress";
-            description = "Database user.";
+            description = lib.mdDoc "Database user.";
           };
 
           passwordFile = mkOption {
             type = types.nullOr types.path;
             default = null;
             example = "/run/keys/wordpress-dbpassword";
-            description = ''
+            description = lib.mdDoc ''
               A file containing the password corresponding to
-              <option>database.user</option>.
+              {option}`database.user`.
             '';
           };
 
           tablePrefix = mkOption {
             type = types.str;
             default = "wp_";
-            description = ''
+            description = lib.mdDoc ''
               The $table_prefix is the value placed in the front of your database tables.
               Change the value if you want to use something other than wp_ for your database
               prefix. Typically this is changed if you are installing multiple WordPress blogs
               in the same database.
 
-              See <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php#table_prefix'/>.
+              See <https://codex.wordpress.org/Editing_wp-config.php#table_prefix>.
             '';
           };
 
@@ -200,13 +243,13 @@ let
             type = types.nullOr types.path;
             default = null;
             defaultText = literalExpression "/run/mysqld/mysqld.sock";
-            description = "Path to the unix socket file to use for authentication.";
+            description = lib.mdDoc "Path to the unix socket file to use for authentication.";
           };
 
           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.";
           };
         };
 
@@ -219,8 +262,8 @@ let
               enableACME = true;
             }
           '';
-          description = ''
-            Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+          description = lib.mdDoc ''
+            Apache configuration can be done by adapting {option}`services.httpd.virtualHosts`.
           '';
         };
 
@@ -234,8 +277,8 @@ let
             "pm.max_spare_servers" = 4;
             "pm.max_requests" = 500;
           };
-          description = ''
-            Options for the WordPress PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+          description = lib.mdDoc ''
+            Options for the WordPress PHP pool. See the documentation on `php-fpm.conf`
             for details on configuration directives.
           '';
         };
@@ -243,10 +286,10 @@ let
         extraConfig = mkOption {
           type = types.lines;
           default = "";
-          description = ''
+          description = lib.mdDoc ''
             Any additional text to be appended to the wp-config.php
             configuration file. This is a PHP script. For configuration
-            settings, see <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php'/>.
+            settings, see <https://codex.wordpress.org/Editing_wp-config.php>.
           '';
           example = ''
             define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
@@ -265,20 +308,20 @@ in
       sites = mkOption {
         type = types.attrsOf (types.submodule siteOpts);
         default = {};
-        description = "Specification of one or more WordPress sites to serve";
+        description = lib.mdDoc "Specification of one or more WordPress sites to serve";
       };
 
       webserver = mkOption {
         type = types.enum [ "httpd" "nginx" "caddy" ];
         default = "httpd";
-        description = ''
+        description = lib.mdDoc ''
           Whether to use apache2 or nginx for virtual host management.
 
-          Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
-          See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+          Further 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 <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
-          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+          Further apache2 configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
+          See [](#opt-services.httpd.virtualHosts) for further information.
         '';
       };
 
@@ -366,6 +409,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/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix
new file mode 100644
index 000000000000..dec00b46f335
--- /dev/null
+++ b/nixos/modules/services/web-apps/writefreely.nix
@@ -0,0 +1,485 @@
+{ 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' = if builtins.isNull value then
+          ""
+        else 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/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix
index b83265ffeab6..09a2b9e965c0 100644
--- a/nixos/modules/services/web-apps/youtrack.nix
+++ b/nixos/modules/services/web-apps/youtrack.nix
@@ -21,10 +21,10 @@ in
 {
   options.services.youtrack = {
 
-    enable = mkEnableOption "YouTrack service";
+    enable = mkEnableOption (lib.mdDoc "YouTrack service");
 
     address = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The interface youtrack will listen on.
       '';
       default = "127.0.0.1";
@@ -32,7 +32,7 @@ in
     };
 
     baseUrl = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Base URL for youtrack. Will be auto-detected and stored in database.
       '';
       type = types.nullOr types.str;
@@ -41,7 +41,7 @@ in
 
     extraParams = mkOption {
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Extra parameters to pass to youtrack. See
         https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html
         for more information.
@@ -55,7 +55,7 @@ in
     };
 
     package = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Package to use.
       '';
       type = types.package;
@@ -64,15 +64,15 @@ in
     };
 
     port = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The port youtrack will listen on.
       '';
       default = 8080;
-      type = types.int;
+      type = types.port;
     };
 
     statePath = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Where to keep the youtrack database.
       '';
       type = types.path;
@@ -80,7 +80,7 @@ in
     };
 
     virtualHost = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Name of the nginx virtual host to use and setup.
         If null, do not setup anything.
       '';
@@ -89,7 +89,7 @@ in
     };
 
     jvmOpts = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Extra options to pass to the JVM.
         See https://www.jetbrains.com/help/youtrack/standalone/Configure-JVM-Options.html
         for more information.
@@ -100,7 +100,7 @@ in
     };
 
     maxMemory = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Maximum Java heap size
       '';
       type = types.str;
@@ -108,7 +108,7 @@ in
     };
 
     maxMetaspaceSize = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Maximum java Metaspace memory.
       '';
       type = types.str;
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
index 538dac0d5be2..2cea7e7cea72 100644
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -40,25 +40,25 @@ in
 
   options.services = {
     zabbixWeb = {
-      enable = mkEnableOption "the Zabbix web interface";
+      enable = mkEnableOption (lib.mdDoc "the Zabbix web interface");
 
       package = mkOption {
         type = types.package;
         default = pkgs.zabbix.web;
         defaultText = literalExpression "zabbix.web";
-        description = "Which Zabbix package to use.";
+        description = lib.mdDoc "Which Zabbix package to use.";
       };
 
       server = {
         port = mkOption {
-          type = types.int;
-          description = "The port of the Zabbix server to connect to.";
+          type = types.port;
+          description = lib.mdDoc "The port of the Zabbix server to connect to.";
           default = 10051;
         };
 
         address = mkOption {
           type = types.str;
-          description = "The IP address or hostname of the Zabbix server to connect to.";
+          description = lib.mdDoc "The IP address or hostname of the Zabbix server to connect to.";
           default = "localhost";
         };
       };
@@ -68,17 +68,17 @@ in
           type = types.enum [ "mysql" "pgsql" "oracle" ];
           example = "mysql";
           default = "pgsql";
-          description = "Database engine to use.";
+          description = lib.mdDoc "Database engine to use.";
         };
 
         host = mkOption {
           type = types.str;
           default = "";
-          description = "Database host address.";
+          description = lib.mdDoc "Database host address.";
         };
 
         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
@@ -88,28 +88,28 @@ in
             else if config.${opt.database.type} == "pgsql" then config.${options.services.postgresql.port}
             else 1521
           '';
-          description = "Database host port.";
+          description = lib.mdDoc "Database host port.";
         };
 
         name = mkOption {
           type = types.str;
           default = "zabbix";
-          description = "Database name.";
+          description = lib.mdDoc "Database name.";
         };
 
         user = mkOption {
           type = types.str;
           default = "zabbix";
-          description = "Database user.";
+          description = lib.mdDoc "Database user.";
         };
 
         passwordFile = mkOption {
           type = types.nullOr types.path;
           default = null;
           example = "/run/keys/zabbix-dbpassword";
-          description = ''
+          description = lib.mdDoc ''
             A file containing the password corresponding to
-            <option>database.user</option>.
+            {option}`database.user`.
           '';
         };
 
@@ -117,7 +117,7 @@ in
           type = types.nullOr types.path;
           default = null;
           example = "/run/postgresql";
-          description = "Path to the unix socket file to use for authentication.";
+          description = lib.mdDoc "Path to the unix socket file to use for authentication.";
         };
       };
 
@@ -131,9 +131,9 @@ in
             enableACME = true;
           }
         '';
-        description = ''
-          Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
-          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+        description = lib.mdDoc ''
+          Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
+          See [](#opt-services.httpd.virtualHosts) for further information.
         '';
       };
 
@@ -147,16 +147,16 @@ in
           "pm.max_spare_servers" = 4;
           "pm.max_requests" = 500;
         };
-        description = ''
-          Options for the Zabbix PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+        description = lib.mdDoc ''
+          Options for the Zabbix PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
         '';
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Additional configuration to be copied verbatim into <filename>zabbix.conf.php</filename>.
+        description = lib.mdDoc ''
+          Additional configuration to be copied verbatim into {file}`zabbix.conf.php`.
         '';
       };
 
diff --git a/nixos/modules/services/web-servers/agate.nix b/nixos/modules/services/web-servers/agate.nix
index 3afdb561c0b0..a0c8a8c94ee5 100644
--- a/nixos/modules/services/web-servers/agate.nix
+++ b/nixos/modules/services/web-servers/agate.nix
@@ -8,19 +8,19 @@ in
 {
   options = {
     services.agate = {
-      enable = mkEnableOption "Agate Server";
+      enable = mkEnableOption (lib.mdDoc "Agate Server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.agate;
         defaultText = literalExpression "pkgs.agate";
-        description = "The package to use";
+        description = lib.mdDoc "The package to use";
       };
 
       addresses = mkOption {
         type = types.listOf types.str;
         default = [ "0.0.0.0:1965" ];
-        description = ''
+        description = lib.mdDoc ''
           Addresses to listen on, IP:PORT, if you haven't disabled forwarding
           only set IPv4.
         '';
@@ -29,41 +29,41 @@ in
       contentDir = mkOption {
         default = "/var/lib/agate/content";
         type = types.path;
-        description = "Root of the content directory.";
+        description = lib.mdDoc "Root of the content directory.";
       };
 
       certificatesDir = mkOption {
         default = "/var/lib/agate/certificates";
         type = types.path;
-        description = "Root of the certificate directory.";
+        description = lib.mdDoc "Root of the certificate directory.";
       };
 
       hostnames = mkOption {
         default = [ ];
         type = types.listOf types.str;
-        description = ''
+        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)
         '';
       };
 
       language = mkOption {
         default = null;
         type = types.nullOr types.str;
-        description = "RFC 4646 Language code for text/gemini documents.";
+        description = lib.mdDoc "RFC 4646 Language code for text/gemini documents.";
       };
 
       onlyTls_1_3 = mkOption {
         default = false;
         type = types.bool;
-        description = "Only use TLSv1.3 (default also allows TLSv1.2).";
+        description = lib.mdDoc "Only use TLSv1.3 (default also allows TLSv1.2).";
       };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
         default = [ "" ];
         example = [ "--log-ip" ];
-        description = "Extra arguments to use running agate.";
+        description = lib.mdDoc "Extra arguments to use running agate.";
       };
     };
   };
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 3099705acbe2..588f5ee4d003 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/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,13 +404,13 @@ in
 
     services.httpd = {
 
-      enable = mkEnableOption "the Apache HTTP Server";
+      enable = mkEnableOption (lib.mdDoc "the Apache HTTP Server");
 
       package = mkOption {
         type = types.package;
         default = pkgs.apacheHttpd;
         defaultText = literalExpression "pkgs.apacheHttpd";
-        description = ''
+        description = lib.mdDoc ''
           Overridable attribute of the Apache HTTP Server package to use.
         '';
       };
@@ -420,7 +420,7 @@ in
         default = confFile;
         defaultText = literalExpression "confFile";
         example = literalExpression ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
-        description = ''
+        description = lib.mdDoc ''
           Override the configuration file used by Apache. By default,
           NixOS generates one automatically.
         '';
@@ -429,10 +429,10 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Configuration lines appended to the generated Apache
           configuration file. Note that this mechanism will not work
-          when <option>configFile</option> is overridden.
+          when {option}`configFile` is overridden.
         '';
       };
 
@@ -445,60 +445,61 @@ 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";
-        description = "E-mail address of the server administrator.";
+        default = null;
+        description = lib.mdDoc "E-mail address of the server administrator.";
       };
 
       logFormat = mkOption {
         type = types.str;
         default = "common";
         example = "combined";
-        description = ''
+        description = lib.mdDoc ''
           Log format for log files. Possible values are: combined, common, referer, agent, none.
-          See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details.
+          See <https://httpd.apache.org/docs/2.4/logs.html> for more details.
         '';
       };
 
       logPerVirtualHost = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           If enabled, each virtual host gets its own
-          <filename>access.log</filename> and
-          <filename>error.log</filename>, namely suffixed by the
-          <option>hostName</option> of the virtual host.
+          {file}`access.log` and
+          {file}`error.log`, namely suffixed by the
+          {option}`hostName` of the virtual host.
         '';
       };
 
       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>
+          ```
         '';
       };
 
       group = mkOption {
         type = types.str;
         default = "wwwrun";
-        description = ''
+        description = lib.mdDoc ''
           Group under which httpd children processes run.
         '';
       };
@@ -506,7 +507,7 @@ in
       logDir = mkOption {
         type = types.path;
         default = "/var/log/httpd";
-        description = ''
+        description = lib.mdDoc ''
           Directory for Apache's log files. It is created automatically.
         '';
       };
@@ -537,7 +538,7 @@ in
             };
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Specification of the virtual hosts served by Apache. Each
           element should be an attribute set specifying the
           configuration of the virtual host.
@@ -547,20 +548,20 @@ in
       enableMellon = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the mod_auth_mellon module.";
+        description = lib.mdDoc "Whether to enable the mod_auth_mellon module.";
       };
 
       enablePHP = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the PHP module.";
+        description = lib.mdDoc "Whether to enable the PHP module.";
       };
 
       phpPackage = mkOption {
         type = types.package;
         default = pkgs.php;
         defaultText = literalExpression "pkgs.php";
-        description = ''
+        description = lib.mdDoc ''
           Overridable attribute of the PHP package to use.
         '';
       };
@@ -568,7 +569,7 @@ in
       enablePerl = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Perl module (mod_perl).";
+        description = lib.mdDoc "Whether to enable the Perl module (mod_perl).";
       };
 
       phpOptions = mkOption {
@@ -578,8 +579,8 @@ in
           ''
             date.timezone = "CET"
           '';
-        description = ''
-          Options appended to the PHP configuration file <filename>php.ini</filename>.
+        description = lib.mdDoc ''
+          Options appended to the PHP configuration file {file}`php.ini`.
         '';
       };
 
@@ -588,13 +589,13 @@ in
         default = "event";
         example = "worker";
         description =
-          ''
+          lib.mdDoc ''
             Multi-processing module to be used by Apache. Available
-            modules are <literal>prefork</literal> (handles each
-            request in a separate child process), <literal>worker</literal>
+            modules are `prefork` (handles each
+            request in a separate child process), `worker`
             (hybrid approach that starts a number of child processes
-            each running a number of threads) and <literal>event</literal>
-            (the default; a recent variant of <literal>worker</literal>
+            each running a number of threads) and `event`
+            (the default; a recent variant of `worker`
             that handles persistent connections more efficiently).
           '';
       };
@@ -603,14 +604,14 @@ in
         type = types.int;
         default = 150;
         example = 8;
-        description = "Maximum number of httpd processes (prefork)";
+        description = lib.mdDoc "Maximum number of httpd processes (prefork)";
       };
 
       maxRequestsPerChild = mkOption {
         type = types.int;
         default = 0;
         example = 500;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
         '';
       };
@@ -618,14 +619,14 @@ in
       sslCiphers = mkOption {
         type = types.str;
         default = "HIGH:!aNULL:!MD5:!EXP";
-        description = "Cipher Suite available for negotiation in SSL proxy handshake.";
+        description = lib.mdDoc "Cipher Suite available for negotiation in SSL proxy handshake.";
       };
 
       sslProtocols = mkOption {
         type = types.str;
         default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
         example = "All -SSLv2 -SSLv3";
-        description = "Allowed SSL/TLS protocol versions.";
+        description = lib.mdDoc "Allowed SSL/TLS protocol versions.";
       };
     };
 
@@ -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/nixos/modules/services/web-servers/apache-httpd/location-options.nix b/nixos/modules/services/web-servers/apache-httpd/location-options.nix
index 8ea88f94f973..f2d4f8357047 100644
--- a/nixos/modules/services/web-servers/apache-httpd/location-options.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/location-options.nix
@@ -9,8 +9,8 @@ in
       type = with types; nullOr str;
       default = null;
       example = "http://www.example.org/";
-      description = ''
-        Sets up a simple reverse proxy as described by <link xlink:href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html#simple" />.
+      description = lib.mdDoc ''
+        Sets up a simple reverse proxy as described by <https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html#simple>.
       '';
     };
 
@@ -18,8 +18,8 @@ in
       type = with types; nullOr str;
       default = null;
       example = "index.php index.html";
-      description = ''
-        Adds DirectoryIndex directive. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex" />.
+      description = lib.mdDoc ''
+        Adds DirectoryIndex directive. See <https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex>.
       '';
     };
 
@@ -27,15 +27,15 @@ in
       type = with types; nullOr path;
       default = null;
       example = "/your/alias/directory";
-      description = ''
-        Alias directory for requests. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias" />.
+      description = lib.mdDoc ''
+        Alias directory for requests. See <https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias>.
       '';
     };
 
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         These lines go to the end of the location verbatim.
       '';
     };
@@ -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/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix b/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
index c52ab2c596e0..7b87f9ef4bde 100644
--- a/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
@@ -8,14 +8,14 @@ in
     hostName = mkOption {
       type = types.str;
       default = name;
-      description = "Canonical hostname for the server.";
+      description = lib.mdDoc "Canonical hostname for the server.";
     };
 
     serverAliases = mkOption {
       type = types.listOf types.str;
       default = [];
       example = ["www.example.org" "www.example.org:8080" "example.org"];
-      description = ''
+      description = lib.mdDoc ''
         Additional names of virtual hosts served by this virtual host configuration.
       '';
     };
@@ -25,17 +25,17 @@ in
         options = {
           port = mkOption {
             type = types.port;
-            description = "Port to listen on";
+            description = lib.mdDoc "Port to listen on";
           };
           ip = mkOption {
             type = types.str;
             default = "*";
-            description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all.";
+            description = lib.mdDoc "IP to listen on. 0.0.0.0 for IPv4 only, * for all.";
           };
           ssl = mkOption {
             type = types.bool;
             default = false;
-            description = "Whether to enable SSL (https) support.";
+            description = lib.mdDoc "Whether to enable SSL (https) support.";
           };
         };
       }));
@@ -45,25 +45,23 @@ 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`.
+        :::
       '';
     };
 
     listenAddresses = mkOption {
       type = with types; nonEmptyListOf str;
 
-      description = ''
+      description = lib.mdDoc ''
         Listen addresses for this virtual host.
-        Compared to <literal>listen</literal> this only sets the addreses
+        Compared to `listen` this only sets the addresses
         and the ports are chosen automatically.
       '';
       default = [ "*" ];
@@ -79,9 +77,9 @@ in
     addSSL = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
-        <literal>listen</literal> to listen on all interfaces on the respective default
+        `listen` to listen on all interfaces on the respective default
         ports (80, 443).
       '';
     };
@@ -89,19 +87,19 @@ in
     onlySSL = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable HTTPS and reject plain HTTP connections. This will set
-        defaults for <literal>listen</literal> to listen on all interfaces on port 443.
+        defaults for `listen` to listen on all interfaces on port 443.
       '';
     };
 
     forceSSL = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to add a separate nginx server block that permanently redirects (301)
         all plain HTTP traffic to HTTPS. This will set defaults for
-        <literal>listen</literal> to listen on all interfaces on the respective default
+        `listen` to listen on all interfaces on the respective default
         ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
       '';
     };
@@ -109,28 +107,28 @@ in
     enableACME = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to ask Let's Encrypt to sign a certificate for this vhost.
-        Alternately, you can use an existing certificate through <option>useACMEHost</option>.
+        Alternately, you can use an existing certificate through {option}`useACMEHost`.
       '';
     };
 
     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).*
       '';
     };
 
     acmeRoot = mkOption {
       type = types.nullOr types.str;
       default = "/var/lib/acme/acme-challenge";
-      description = ''
+      description = lib.mdDoc ''
         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.
       '';
@@ -139,28 +137,28 @@ in
     sslServerCert = mkOption {
       type = types.path;
       example = "/var/host.cert";
-      description = "Path to server SSL certificate.";
+      description = lib.mdDoc "Path to server SSL certificate.";
     };
 
     sslServerKey = mkOption {
       type = types.path;
       example = "/var/host.key";
-      description = "Path to server SSL certificate key.";
+      description = lib.mdDoc "Path to server SSL certificate key.";
     };
 
     sslServerChain = mkOption {
       type = types.nullOr types.path;
       default = null;
       example = "/var/ca.pem";
-      description = "Path to server SSL chain file.";
+      description = lib.mdDoc "Path to server SSL chain file.";
     };
 
     http2 = mkOption {
       type = types.bool;
       default = true;
-      description = ''
-        Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. <emphasis>However, if you use the prefork mpm, there will
-        be severe restrictions.</emphasis> Refer to <link xlink:href="https://httpd.apache.org/docs/2.4/howto/http2.html#mpm-config"/> for details.
+      description = lib.mdDoc ''
+        Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. *However, if you use the prefork mpm, there will
+        be severe restrictions.* Refer to <https://httpd.apache.org/docs/2.4/howto/http2.html#mpm-config> for details.
       '';
     };
 
@@ -168,14 +166,14 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "admin@example.org";
-      description = "E-mail address of the server administrator.";
+      description = lib.mdDoc "E-mail address of the server administrator.";
     };
 
     documentRoot = mkOption {
       type = types.nullOr types.path;
       default = null;
       example = "/data/webserver/docs";
-      description = ''
+      description = lib.mdDoc ''
         The path of Apache's document root directory.  If left undefined,
         an empty directory in the Nix store will be used as root.
       '';
@@ -189,7 +187,7 @@ in
           dir = "/home/eelco/Dev/nix-homepage";
         }
       ];
-      description = ''
+      description = lib.mdDoc ''
         This option provides a simple way to serve static directories.
       '';
     };
@@ -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.
+        :::
       '';
     };
 
@@ -222,7 +220,7 @@ in
           AllowOverride All
         </Directory>
       '';
-      description = ''
+      description = lib.mdDoc ''
         These lines go to httpd.conf verbatim. They will go after
         directories and directory aliases defined by default.
       '';
@@ -231,9 +229,9 @@ in
     enableUserDir = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to enable serving <filename>~/public_html</filename> as
-        <literal>/~<replaceable>username</replaceable></literal>.
+      description = lib.mdDoc ''
+        Whether to enable serving {file}`~/public_html` as
+        `/~«username»`.
       '';
     };
 
@@ -241,7 +239,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "http://newserver.example.org/";
-      description = ''
+      description = lib.mdDoc ''
         If set, all requests for this host are redirected permanently to
         the given URL.
       '';
@@ -251,7 +249,7 @@ in
       type = types.str;
       default = "common";
       example = "combined";
-      description = ''
+      description = lib.mdDoc ''
         Log format for Apache's log files. Possible values are: combined, common, referer, agent.
       '';
     };
@@ -260,9 +258,8 @@ in
       type = types.lines;
       default = "";
       example = "Disallow: /foo/";
-      description = ''
-        Specification of pages to be ignored by web crawlers. See <link
-        xlink:href='http://www.robotstxt.org/'/> for details.
+      description = lib.mdDoc ''
+        Specification of pages to be ignored by web crawlers. See <http://www.robotstxt.org/> for details.
       '';
     };
 
@@ -279,9 +276,8 @@ in
           };
         };
       '';
-      description = ''
-        Declarative location config. See <link
-        xlink:href="https://httpd.apache.org/docs/2.4/mod/core.html#location"/> for details.
+      description = lib.mdDoc ''
+        Declarative location config. See <https://httpd.apache.org/docs/2.4/mod/core.html#location> for details.
       '';
     };
 
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index 2b8c6f2e308b..50213ec252ff 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/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,11 @@ let
       '';
 
       Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } ''
-        ${cfg.package}/bin/caddy fmt ${Caddyfile} > $out
+        mkdir -p $out
+        ${cfg.package}/bin/caddy fmt ${Caddyfile}/Caddyfile > $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 +53,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.
+        :::
       '';
     };
 
@@ -86,7 +87,7 @@ in
       default = pkgs.caddy;
       defaultText = literalExpression "pkgs.caddy";
       type = types.package;
-      description = ''
+      description = lib.mdDoc ''
         Caddy package to use.
       '';
     };
@@ -94,34 +95,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.
+        :::
       '';
     };
 
@@ -133,9 +131,9 @@ in
       example = literalExpression ''
         mkForce "level INFO";
       '';
-      description = ''
+      description = lib.mdDoc ''
         Configuration for the default logger. See
-        <link xlink:href="https://caddyserver.com/docs/caddyfile/options#log"/>
+        <https://caddyserver.com/docs/caddyfile/options#log>
         for details.
       '';
     };
@@ -145,7 +143,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
@@ -153,33 +151,40 @@ in
           file_server
         ''';
       '';
-      description = ''
+      description = lib.mdDoc ''
         Override the configuration file used by Caddy. By default,
         NixOS generates one automatically.
       '';
     };
 
     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`.
+        :::
       '';
     };
 
     resume = mkOption {
       default = false;
       type = types.bool;
-      description = ''
-        Use saved config, if any (and prefer over any specified configuration passed with <literal>--config</literal>).
+      description = lib.mdDoc ''
+        Use saved config, if any (and prefer over any specified configuration passed with `--config`).
       '';
     };
 
@@ -194,11 +199,11 @@ in
           }
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Additional lines of configuration appended to the global config section
-        of the <literal>Caddyfile</literal>.
+        of the `Caddyfile`.
 
-        Refer to <link xlink:href="https://caddyserver.com/docs/caddyfile/options#global-options"/>
+        Refer to <https://caddyserver.com/docs/caddyfile/options#global-options>
         for details on supported values.
       '';
     };
@@ -213,9 +218,9 @@ in
           root /srv/http
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Additional lines of configuration appended to the automatically
-        generated <literal>Caddyfile</literal>.
+        generated `Caddyfile`.
       '';
     };
 
@@ -233,7 +238,7 @@ in
           };
         };
       '';
-      description = ''
+      description = lib.mdDoc ''
         Declarative specification of virtual hosts served by Caddy.
       '';
     };
@@ -242,11 +247,11 @@ in
       default = "https://acme-v02.api.letsencrypt.org/directory";
       example = "https://acme-staging-v02.api.letsencrypt.org/directory";
       type = with types; nullOr str;
-      description = ''
+      description = lib.mdDoc ''
         The URL to the ACME CA's directory. It is strongly recommended to set
         this to Let's Encrypt's staging endpoint for testing or development.
 
-        Set it to <literal>null</literal> if you want to write a more
+        Set it to `null` if you want to write a more
         fine-grained configuration manually.
       '';
     };
@@ -254,7 +259,7 @@ in
     email = mkOption {
       default = null;
       type = with types; nullOr str;
-      description = ''
+      description = lib.mdDoc ''
         Your email address. Mainly used when creating an ACME account with your
         CA, and is highly recommended in case there are problems with your
         certificates.
@@ -267,8 +272,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 +290,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 +306,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}" ];
-
-        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 +337,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/nixos/modules/services/web-servers/caddy/vhost-options.nix b/nixos/modules/services/web-servers/caddy/vhost-options.nix
index f240ec605c29..229b53efb49f 100644
--- a/nixos/modules/services/web-servers/caddy/vhost-options.nix
+++ b/nixos/modules/services/web-servers/caddy/vhost-options.nix
@@ -9,21 +9,21 @@ in
     hostName = mkOption {
       type = types.str;
       default = name;
-      description = "Canonical hostname for the server.";
+      description = lib.mdDoc "Canonical hostname for the server.";
     };
 
     serverAliases = mkOption {
       type = with types; listOf str;
       default = [ ];
       example = [ "www.example.org" "example.org" ];
-      description = ''
+      description = lib.mdDoc ''
         Additional names of virtual hosts served by this virtual host configuration.
       '';
     };
 
     listenAddresses = mkOption {
       type = with types; listOf str;
-      description = ''
+      description = lib.mdDoc ''
         A list of host interfaces to bind to for this virtual host.
       '';
       default = [ ];
@@ -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).*
       '';
     };
 
@@ -59,9 +57,9 @@ in
           output discard
         ''';
       '';
-      description = ''
+      description = lib.mdDoc ''
         Configuration for HTTP request logging (also known as access logs). See
-        <link xlink:href="https://caddyserver.com/docs/caddyfile/directives/log#log"/>
+        <https://caddyserver.com/docs/caddyfile/directives/log#log>
         for details.
       '';
     };
@@ -69,9 +67,9 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Additional lines of configuration appended to this virtual host in the
-        automatically generated <literal>Caddyfile</literal>.
+        automatically generated `Caddyfile`.
       '';
     };
 
diff --git a/nixos/modules/services/web-servers/darkhttpd.nix b/nixos/modules/services/web-servers/darkhttpd.nix
index f6b693139a1e..1e3a7166bc41 100644
--- a/nixos/modules/services/web-servers/darkhttpd.nix
+++ b/nixos/modules/services/web-servers/darkhttpd.nix
@@ -15,12 +15,12 @@ let
 
 in {
   options.services.darkhttpd = with types; {
-    enable = mkEnableOption "DarkHTTPd web server";
+    enable = mkEnableOption (lib.mdDoc "DarkHTTPd web server");
 
     port = mkOption {
       default = 80;
       type = types.port;
-      description = ''
+      description = lib.mdDoc ''
         Port to listen on.
         Pass 0 to let the system choose any free port for you.
       '';
@@ -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.
       '';
@@ -37,7 +37,7 @@ in {
 
     rootDir = mkOption {
       type = path;
-      description = ''
+      description = lib.mdDoc ''
         Path from which to serve files.
       '';
     };
@@ -45,7 +45,7 @@ in {
     hideServerId = mkOption {
       type = bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Don't identify the server type in headers or directory listings.
       '';
     };
@@ -53,7 +53,7 @@ in {
     extraArgs = mkOption {
       type = listOf str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         Additional configuration passed to the executable.
       '';
     };
diff --git a/nixos/modules/services/web-servers/fcgiwrap.nix b/nixos/modules/services/web-servers/fcgiwrap.nix
index a64a187255a4..f9c91fb35db2 100644
--- a/nixos/modules/services/web-servers/fcgiwrap.nix
+++ b/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -11,38 +11,38 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI.";
+        description = lib.mdDoc "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI.";
       };
 
       preforkProcesses = mkOption {
         type = types.int;
         default = 1;
-        description = "Number of processes to prefork.";
+        description = lib.mdDoc "Number of processes to prefork.";
       };
 
       socketType = mkOption {
         type = types.enum [ "unix" "tcp" "tcp6" ];
         default = "unix";
-        description = "Socket type: 'unix', 'tcp' or 'tcp6'.";
+        description = lib.mdDoc "Socket type: 'unix', 'tcp' or 'tcp6'.";
       };
 
       socketAddress = mkOption {
         type = types.str;
         default = "/run/fcgiwrap.sock";
         example = "1.2.3.4:5678";
-        description = "Socket address. In case of a UNIX socket, this should be its filesystem path.";
+        description = lib.mdDoc "Socket address. In case of a UNIX socket, this should be its filesystem path.";
       };
 
       user = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "User permissions for the socket.";
+        description = lib.mdDoc "User permissions for the socket.";
       };
 
       group = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Group permissions for the socket.";
+        description = lib.mdDoc "Group permissions for the socket.";
       };
     };
   };
diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix
new file mode 100644
index 000000000000..76ab273483eb
--- /dev/null
+++ b/nixos/modules/services/web-servers/garage.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.garage;
+  toml = pkgs.formats.toml {};
+  configFile = toml.generate "garage.toml" cfg.settings;
+in
+{
+  meta.maintainers = [ 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" 1 2 3 ]);
+            apply = v: toString v;
+            description = lib.mdDoc "Garage replication mode, defaults to none, see: <https://garagehq.deuxfleurs.fr/reference_manual/configuration.html#replication_mode> for reference.";
+          };
+        };
+      };
+      description = lib.mdDoc "Garage configuration, see <https://garagehq.deuxfleurs.fr/reference_manual/configuration.html> for reference.";
+    };
+
+    package = mkOption {
+      default = pkgs.garage;
+      defaultText = literalExpression "pkgs.garage";
+      type = types.package;
+      description = lib.mdDoc "Garage package to use.";
+    };
+  };
+
+  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" ];
+      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/nixos/modules/services/web-servers/hitch/default.nix b/nixos/modules/services/web-servers/hitch/default.nix
index 1812f225b74d..6c8b3cda5f72 100644
--- a/nixos/modules/services/web-servers/hitch/default.nix
+++ b/nixos/modules/services/web-servers/hitch/default.nix
@@ -17,11 +17,11 @@ with lib;
 {
   options = {
     services.hitch = {
-      enable = mkEnableOption "Hitch Server";
+      enable = mkEnableOption (lib.mdDoc "Hitch Server");
 
       backend = mkOption {
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The host and port Hitch connects to when receiving
           a connection in the form [HOST]:PORT
         '';
@@ -30,15 +30,15 @@ with lib;
       ciphers = mkOption {
         type = types.str;
         default = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
-        description = "The list of ciphers to use";
+        description = lib.mdDoc "The list of ciphers to use";
       };
 
       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;
       };
@@ -46,33 +46,33 @@ with lib;
       pem-files = mkOption {
         type = types.listOf types.path;
         default = [];
-        description = "PEM files to use";
+        description = lib.mdDoc "PEM files to use";
       };
 
       ocsp-stapling = {
         enabled = mkOption {
           type = types.bool;
           default = true;
-          description = "Whether to enable OCSP Stapling";
+          description = lib.mdDoc "Whether to enable OCSP Stapling";
         };
       };
 
       user = mkOption {
         type = types.str;
         default = "hitch";
-        description = "The user to run as";
+        description = lib.mdDoc "The user to run as";
       };
 
       group = mkOption {
         type = types.str;
         default = "hitch";
-        description = "The group to run as";
+        description = lib.mdDoc "The group to run as";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional configuration lines";
+        description = lib.mdDoc "Additional configuration lines";
       };
     };
 
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
index 46f62a9119f0..4434965b217a 100644
--- a/nixos/modules/services/web-servers/hydron.nix
+++ b/nixos/modules/services/web-servers/hydron.nix
@@ -4,25 +4,24 @@ let
   cfg = config.services.hydron;
 in with lib; {
   options.services.hydron = {
-    enable = mkEnableOption "hydron";
+    enable = mkEnableOption (lib.mdDoc "hydron");
 
     dataDir = mkOption {
       type = types.path;
       default = "/var/lib/hydron";
       example = "/home/okina/hydron";
-      description = "Location where hydron runs and stores data.";
+      description = lib.mdDoc "Location where hydron runs and stores data.";
     };
 
     interval = mkOption {
       type = types.str;
       default = "weekly";
       example = "06:00";
-      description = ''
+      description = lib.mdDoc ''
         How often we run hydron import and possibly fetch tags. Runs by default every week.
 
         The format is described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.
+        {manpage}`systemd.time(7)`.
       '';
     };
 
@@ -30,19 +29,19 @@ in with lib; {
       type = types.str;
       default = "hydron";
       example = "dumbpass";
-      description = "Password for the hydron database.";
+      description = lib.mdDoc "Password for the hydron database.";
     };
 
     passwordFile = mkOption {
       type = types.path;
       default = "/run/keys/hydron-password-file";
       example = "/home/okina/hydron/keys/pass";
-      description = "Password file for the hydron database.";
+      description = lib.mdDoc "Password file for the hydron database.";
     };
 
     postgresArgs = mkOption {
       type = types.str;
-      description = "Postgresql connection arguments.";
+      description = lib.mdDoc "Postgresql connection arguments.";
       example = ''
         {
           "driver": "postgres",
@@ -55,27 +54,27 @@ in with lib; {
       type = types.path;
       default = "/run/keys/hydron-postgres-args";
       example = "/home/okina/hydron/keys/postgres";
-      description = "Postgresql connection arguments file.";
+      description = lib.mdDoc "Postgresql connection arguments file.";
     };
 
     listenAddress = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "127.0.0.1:8010";
-      description = "Listen on a specific IP address and port.";
+      description = lib.mdDoc "Listen on a specific IP address and port.";
     };
 
     importPaths = mkOption {
       type = types.listOf types.path;
       default = [];
       example = [ "/home/okina/Pictures" ];
-      description = "Paths that hydron will recursively import.";
+      description = lib.mdDoc "Paths that hydron will recursively import.";
     };
 
     fetchTags = mkOption {
       type = types.bool;
       default = true;
-      description = "Fetch tags for imported images and webm from gelbooru.";
+      description = lib.mdDoc "Fetch tags for imported images and webm from gelbooru.";
     };
   };
 
diff --git a/nixos/modules/services/web-servers/jboss/default.nix b/nixos/modules/services/web-servers/jboss/default.nix
index d243e0f3f1b7..05b354d567fe 100644
--- a/nixos/modules/services/web-servers/jboss/default.nix
+++ b/nixos/modules/services/web-servers/jboss/default.nix
@@ -26,49 +26,49 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities.";
+        description = lib.mdDoc "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities.";
       };
 
       tempDir = mkOption {
         default = "/tmp";
         type = types.str;
-        description = "Location where JBoss stores its temp files";
+        description = lib.mdDoc "Location where JBoss stores its temp files";
       };
 
       logDir = mkOption {
         default = "/var/log/jboss";
         type = types.str;
-        description = "Location of the logfile directory of JBoss";
+        description = lib.mdDoc "Location of the logfile directory of JBoss";
       };
 
       serverDir = mkOption {
-        description = "Location of the server instance files";
+        description = lib.mdDoc "Location of the server instance files";
         default = "/var/jboss/server";
         type = types.str;
       };
 
       deployDir = mkOption {
-        description = "Location of the deployment files";
+        description = lib.mdDoc "Location of the deployment files";
         default = "/nix/var/nix/profiles/default/server/default/deploy/";
         type = types.str;
       };
 
       libUrl = mkOption {
         default = "file:///nix/var/nix/profiles/default/server/default/lib";
-        description = "Location where the shared library JARs are stored";
+        description = lib.mdDoc "Location where the shared library JARs are stored";
         type = types.str;
       };
 
       user = mkOption {
         default = "nobody";
-        description = "User account under which jboss runs.";
+        description = lib.mdDoc "User account under which jboss runs.";
         type = types.str;
       };
 
       useJK = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to use to connector to the Apache HTTP server";
+        description = lib.mdDoc "Whether to use to connector to the Apache HTTP server";
       };
 
     };
diff --git a/nixos/modules/services/web-servers/keter/bundle.nix b/nixos/modules/services/web-servers/keter/bundle.nix
new file mode 100644
index 000000000000..32b08c3be206
--- /dev/null
+++ b/nixos/modules/services/web-servers/keter/bundle.nix
@@ -0,0 +1,40 @@
+/* This makes a keter bundle as described on the github page:
+  https://github.com/snoyberg/keter#bundling-your-app-for-keter
+*/
+{ keterDomain
+, keterExecutable
+, gnutar
+, writeTextFile
+, lib
+, stdenv
+, ...
+}:
+
+let
+  str.stanzas = [{
+    # we just use nix as an absolute path so we're not bundling any binaries
+    type = "webapp";
+    /* Note that we're not actually putting the executable in the bundle,
+      we already can use the nix store for copying, so we just
+      symlink to the app. */
+    exec = keterExecutable;
+    host = keterDomain;
+  }];
+  configFile = writeTextFile {
+    name = "keter.yml";
+    text = (lib.generators.toYAML { } str);
+  };
+
+in
+stdenv.mkDerivation {
+  name = "keter-bundle";
+  buildCommand = ''
+    mkdir -p config
+    cp ${configFile} config/keter.yaml
+
+    echo 'create a gzipped tarball'
+    mkdir -p $out
+    tar -zcvf $out/bundle.tar.gz.keter ./.
+  '';
+  buildInputs = [ gnutar ];
+}
diff --git a/nixos/modules/services/web-servers/keter/default.nix b/nixos/modules/services/web-servers/keter/default.nix
new file mode 100644
index 000000000000..9adbe65de69f
--- /dev/null
+++ b/nixos/modules/services/web-servers/keter/default.nix
@@ -0,0 +1,162 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.keter;
+in
+{
+  meta = {
+    maintainers = with lib.maintainers; [ jappie ];
+  };
+
+  options.services.keter = {
+    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 = 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 = lib.mdDoc "The keter package to be used";
+    };
+
+    globalKeterConfig = lib.mkOption {
+      type = lib.types.attrs;
+      default = {
+        ip-from-header = true;
+        listeners = [{
+          host = "*4";
+          port = 6981;
+        }];
+      };
+      # You want that ip-from-header in the nginx setup case
+      # so it's not set to 127.0.0.1.
+      # using a port above 1024 allows you to avoid needing CAP_NET_BIND_SERVICE
+      defaultText = lib.literalExpression ''
+        {
+          ip-from-header = true;
+          listeners = [{
+            host = "*4";
+            port = 6981;
+          }];
+        }
+      '';
+      description = lib.mdDoc "Global config for keter";
+    };
+
+    bundle = {
+      appName = lib.mkOption {
+        type = lib.types.str;
+        default = "myapp";
+        description = lib.mdDoc "The name keter assigns to this bundle";
+      };
+
+      executable = lib.mkOption {
+        type = lib.types.path;
+        description = lib.mdDoc "The executable to be run";
+      };
+
+      domain = lib.mkOption {
+        type = lib.types.str;
+        default = "example.com";
+        description = lib.mdDoc "The domain keter will bind to";
+      };
+
+      publicScript = lib.mkOption {
+        type = lib.types.str;
+        default = "";
+        description = lib.mdDoc ''
+          Allows loading of public environment variables,
+          these are emitted to the log so it shouldn't contain secrets.
+        '';
+        example = "ADMIN_EMAIL=hi@example.com";
+      };
+
+      secretScript = lib.mkOption {
+        type = lib.types.str;
+        default = "";
+        description = lib.mdDoc "Allows loading of private environment variables";
+        example = "MY_AWS_KEY=$(cat /run/keys/AWS_ACCESS_KEY_ID)";
+      };
+    };
+
+  };
+
+  config = lib.mkIf cfg.enable (
+    let
+      incoming = "${cfg.keterRoot}/incoming";
+
+
+      globalKeterConfigFile = pkgs.writeTextFile {
+        name = "keter-config.yml";
+        text = (lib.generators.toYAML { } (cfg.globalKeterConfig // { root = cfg.keterRoot; }));
+      };
+
+      # If things are expected to change often, put it in the bundle!
+      bundle = pkgs.callPackage ./bundle.nix
+        (cfg.bundle // { keterExecutable = executable; keterDomain = cfg.bundle.domain; });
+
+      # This indirection is required to ensure the nix path
+      # gets copied over to the target machine in remote deployments.
+      # Furthermore, it's important that we use exec to
+      # run the binary otherwise we get process leakage due to this
+      # being executed on every change.
+      executable = pkgs.writeShellScript "bundle-wrapper" ''
+        set -e
+        ${cfg.bundle.secretScript}
+        set -xe
+        ${cfg.bundle.publicScript}
+        exec ${cfg.bundle.executable}
+      '';
+
+    in
+    {
+      systemd.services.keter = {
+        description = "keter app loader";
+        script = ''
+          set -xe
+          mkdir -p ${incoming}
+          { tail -F ${cfg.keterRoot}/log/keter/current.log -n 0 & ${cfg.keterPackage}/bin/keter ${globalKeterConfigFile}; }
+        '';
+        wantedBy = [ "multi-user.target" "nginx.service" ];
+
+        serviceConfig = {
+          Restart = "always";
+          RestartSec = "10s";
+        };
+
+        after = [
+          "network.target"
+          "local-fs.target"
+          "postgresql.service"
+        ];
+      };
+
+      # 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 executable,
+      # we inherit nix based cache busting.
+      systemd.services.load-keter-bundle = {
+        description = "load keter bundle into incoming folder";
+        after = [ "keter.service" ];
+        wantedBy = [ "multi-user.target" ];
+        # we can't override keter bundles because it'll stop the previous app
+        # https://github.com/snoyberg/keter#deploying
+        script = ''
+          set -xe
+          cp ${bundle}/bundle.tar.gz.keter ${incoming}/${cfg.bundle.appName}.keter
+        '';
+        path = [
+          executable
+          cfg.bundle.executable
+        ]; # this is a hack to get the executable copied over to the machine.
+      };
+    }
+  );
+}
diff --git a/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixos/modules/services/web-servers/lighttpd/cgit.nix
index 8cd6d020940b..5042fbf1f8f2 100644
--- a/nixos/modules/services/web-servers/lighttpd/cgit.nix
+++ b/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -23,7 +23,7 @@ in
     enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         If true, enable cgit (fast web interface for git repositories) as a
         sub-service in lighttpd.
       '';
@@ -33,7 +33,7 @@ in
       default = "cgit";
       example = "";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The subdirectory in which to serve cgit. The web application will be
         accessible at http://yourserver/''${subdir}
       '';
@@ -50,7 +50,7 @@ in
         '''
       '';
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Verbatim contents of the cgit runtime configuration file. Documentation
         (with cgitrc example file) is available in "man cgitrc". Or online:
         http://git.zx2c4.com/cgit/tree/cgitrc.5.txt
diff --git a/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixos/modules/services/web-servers/lighttpd/collectd.nix
index 5f091591daf9..9a4285e3e2d2 100644
--- a/nixos/modules/services/web-servers/lighttpd/collectd.nix
+++ b/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -25,15 +25,15 @@ 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 = ''
+      description = lib.mdDoc ''
         Path to collection.cgi script from (collectd sources)/contrib/collection.cgi
         This option allows to use a customized version
       '';
diff --git a/nixos/modules/services/web-servers/lighttpd/default.nix b/nixos/modules/services/web-servers/lighttpd/default.nix
index 05e897c8cc94..811afe8e0af6 100644
--- a/nixos/modules/services/web-servers/lighttpd/default.nix
+++ b/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -130,16 +130,16 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable the lighttpd web server.
         '';
       };
 
       package = mkOption {
         default = pkgs.lighttpd;
-        defaultText = "pkgs.lighttpd";
+        defaultText = lib.literalExpression "pkgs.lighttpd";
         type = types.package;
-        description = ''
+        description = lib.mdDoc ''
           lighttpd package to use.
         '';
       };
@@ -147,7 +147,7 @@ in
       port = mkOption {
         default = 80;
         type = types.port;
-        description = ''
+        description = lib.mdDoc ''
           TCP port number for lighttpd to bind to.
         '';
       };
@@ -155,7 +155,7 @@ in
       document-root = mkOption {
         default = "/srv/www";
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Document-root of the web server. Must be readable by the "lighttpd" user.
         '';
       };
@@ -163,7 +163,7 @@ in
       mod_userdir = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If true, requests in the form /~user/page.html are rewritten to take
           the file public_html/page.html from the home directory of the user.
         '';
@@ -173,11 +173,11 @@ in
         type = types.listOf types.str;
         default = [ ];
         example = [ "mod_cgi" "mod_status" ];
-        description = ''
+        description = lib.mdDoc ''
           List of lighttpd modules to enable. Sub-services take care of
           enabling modules as needed, so this option is mainly for when you
           want to add custom stuff to
-          <option>services.lighttpd.extraConfig</option> that depends on a
+          {option}`services.lighttpd.extraConfig` that depends on a
           certain module.
         '';
       };
@@ -185,18 +185,18 @@ in
       enableUpstreamMimeTypes = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to include the list of mime types bundled with lighttpd
           (upstream). If you disable this, no mime types will be added by
           NixOS and you will have to add your own mime types in
-          <option>services.lighttpd.extraConfig</option>.
+          {option}`services.lighttpd.extraConfig`.
         '';
       };
 
       mod_status = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Show server status overview at /server-status, statistics at
           /server-statistics and list of loaded modules at /server-config.
         '';
@@ -206,7 +206,7 @@ in
         default = "";
         type = types.lines;
         example = "...verbatim config file contents...";
-        description = ''
+        description = lib.mdDoc ''
           Overridable config file contents to use for lighttpd. By default, use
           the contents automatically generated by NixOS.
         '';
@@ -215,10 +215,10 @@ in
       extraConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           These configuration lines will be appended to the generated lighttpd
           config file. Note that this mechanism does not work when the manual
-          <option>configText</option> option is used.
+          {option}`configText` option is used.
         '';
       };
 
diff --git a/nixos/modules/services/web-servers/lighttpd/gitweb.nix b/nixos/modules/services/web-servers/lighttpd/gitweb.nix
index c494d6966a7f..e129e8bc1666 100644
--- a/nixos/modules/services/web-servers/lighttpd/gitweb.nix
+++ b/nixos/modules/services/web-servers/lighttpd/gitweb.nix
@@ -16,7 +16,7 @@ in
     enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         If true, enable gitweb in lighttpd. Access it at http://yourserver/gitweb
       '';
     };
diff --git a/nixos/modules/services/web-servers/merecat.nix b/nixos/modules/services/web-servers/merecat.nix
new file mode 100644
index 000000000000..aad93605b717
--- /dev/null
+++ b/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/nixos/modules/services/web-servers/mighttpd2.nix b/nixos/modules/services/web-servers/mighttpd2.nix
index f9b1a8b6ccce..2d887af87c79 100644
--- a/nixos/modules/services/web-servers/mighttpd2.nix
+++ b/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 = "";
@@ -42,7 +42,7 @@ in {
         Service: 0 # 0 is HTTP only, 1 is HTTPS only, 2 is both
       '';
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Verbatim config file to use
         (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
       '';
@@ -76,7 +76,7 @@ in {
         /                -> /export/www/
       '';
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Verbatim routing file to use
         (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
       '';
@@ -85,7 +85,7 @@ in {
     cores = mkOption {
       default = null;
       type = types.nullOr types.int;
-      description = ''
+      description = lib.mdDoc ''
         How many cores to use.
         If null it will be determined automatically
       '';
diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix
index c345e3f2467b..1a9eacb431b3 100644
--- a/nixos/modules/services/web-servers/minio.nix
+++ b/nixos/modules/services/web-servers/minio.nix
@@ -14,56 +14,56 @@ in
   meta.maintainers = [ maintainers.bachp ];
 
   options.services.minio = {
-    enable = mkEnableOption "Minio Object Storage";
+    enable = mkEnableOption (lib.mdDoc "Minio Object Storage");
 
     listenAddress = mkOption {
       default = ":9000";
       type = types.str;
-      description = "IP address and port of the server.";
+      description = lib.mdDoc "IP address and port of the server.";
     };
 
     consoleAddress = mkOption {
       default = ":9001";
       type = types.str;
-      description = "IP address and port of the web UI (console).";
+      description = lib.mdDoc "IP address and port of the web UI (console).";
     };
 
     dataDir = mkOption {
       default = [ "/var/lib/minio/data" ];
       type = types.listOf types.path;
-      description = "The list of data directories for storing the objects. Use one path for regular operation and the minimum of 4 endpoints for Erasure Code mode.";
+      description = lib.mdDoc "The list of data directories for storing the objects. Use one path for regular operation and the minimum of 4 endpoints for Erasure Code mode.";
     };
 
     configDir = mkOption {
       default = "/var/lib/minio/config";
       type = types.path;
-      description = "The config directory, for the access keys and other settings.";
+      description = lib.mdDoc "The config directory, for the access keys and other settings.";
     };
 
     accessKey = mkOption {
       default = "";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Access key of 5 to 20 characters in length that clients use to access the server.
         This overrides the access key that is generated by minio on first startup and stored inside the
-        <literal>configDir</literal> directory.
+        `configDir` directory.
       '';
     };
 
     secretKey = mkOption {
       default = "";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Specify the Secret key of 8 to 40 characters in length that clients use to access the server.
         This overrides the secret key that is generated by minio on first startup and stored inside the
-        <literal>configDir</literal> directory.
+        `configDir` directory.
       '';
     };
 
     rootCredentialsFile = mkOption  {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         File containing the MINIO_ROOT_USER, default is "minioadmin", and
         MINIO_ROOT_PASSWORD (length >= 8), default is "minioadmin"; in the format of
         an EnvironmentFile=, as described by systemd.exec(5).
@@ -74,7 +74,7 @@ in
     region = mkOption {
       default = "us-east-1";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region.
       '';
     };
@@ -82,14 +82,14 @@ in
     browser = mkOption {
       default = true;
       type = types.bool;
-      description = "Enable or disable access to web UI.";
+      description = lib.mdDoc "Enable or disable access to web UI.";
     };
 
     package = mkOption {
       default = pkgs.minio;
       defaultText = literalExpression "pkgs.minio";
       type = types.package;
-      description = "Minio package to use.";
+      description = lib.mdDoc "Minio package to use.";
     };
   };
 
@@ -102,7 +102,7 @@ in
 
     systemd.services.minio = {
       description = "Minio Object Storage";
-      after = [ "network.target" ];
+      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}";
diff --git a/nixos/modules/services/web-servers/molly-brown.nix b/nixos/modules/services/web-servers/molly-brown.nix
index 0bd8b3316cb3..6d7ca0c12ef7 100644
--- a/nixos/modules/services/web-servers/molly-brown.nix
+++ b/nixos/modules/services/web-servers/molly-brown.nix
@@ -10,12 +10,12 @@ in {
 
   options.services.molly-brown = {
 
-    enable = mkEnableOption "Molly-Brown Gemini server";
+    enable = mkEnableOption (lib.mdDoc "Molly-Brown Gemini server");
 
     port = mkOption {
       default = 1965;
       type = types.port;
-      description = ''
+      description = lib.mdDoc ''
         TCP port for molly-brown to bind to.
       '';
     };
@@ -24,7 +24,7 @@ in {
       type = types.str;
       default = config.networking.hostName;
       defaultText = literalExpression "config.networking.hostName";
-      description = ''
+      description = lib.mdDoc ''
         The hostname to respond to requests for. Requests for URLs with
         other hosts will result in a status 53 (PROXY REQUEST REFUSED)
         response.
@@ -34,37 +34,37 @@ 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>
+        ```
       '';
     };
 
     keyPath = mkOption {
       type = types.path;
       example = "/var/lib/acme/example.com/key.pem";
-      description = "Path to TLS key. See <option>CertPath</option>.";
+      description = lib.mdDoc "Path to TLS key. See {option}`CertPath`.";
     };
 
     docBase = mkOption {
       type = types.path;
       example = "/var/lib/molly-brown";
-      description = "Base directory for Gemini content.";
+      description = lib.mdDoc "Base directory for Gemini content.";
     };
 
     settings = mkOption {
       inherit (settingsFormat) type;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         molly-brown configuration. Refer to
-        <link xlink:href="https://tildegit.org/solderpunk/molly-brown/src/branch/master/example.conf"/>
+        <https://tildegit.org/solderpunk/molly-brown/src/branch/master/example.conf>
         for details on supported values.
       '';
     };
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 0c2333399e81..953f31632934 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -192,14 +192,22 @@ let
 
       server_tokens ${if cfg.serverTokens then "on" else "off"};
 
+      ${optionalString (cfg.proxyCache.enable) ''
+        proxy_cache_path /var/cache/nginx keys_zone=${cfg.proxyCache.keysZoneName}:${cfg.proxyCache.keysZoneSize}
+                                          levels=${cfg.proxyCache.levels}
+                                          use_temp_path=${if cfg.proxyCache.useTempPath then "on" else "off"}
+                                          inactive=${cfg.proxyCache.inactive}
+                                          max_size=${cfg.proxyCache.maxSize};
+      ''}
+
       ${cfg.commonHttpConfig}
 
       ${vhosts}
 
       ${optionalString cfg.statusPage ''
         server {
-          listen 80;
-          ${optionalString enableIPv6 "listen [::]:80;" }
+          listen ${toString cfg.defaultHTTPListenPort};
+          ${optionalString enableIPv6 "listen [::]:${toString cfg.defaultHTTPListenPort};" }
 
           server_name localhost;
 
@@ -233,7 +241,7 @@ let
 
   configPath = if cfg.enableReload
     then "/etc/nginx/nginx.conf"
-    else configFile;
+    else finalConfigFile;
 
   execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
 
@@ -246,8 +254,8 @@ let
           if vhost.listen != [] then vhost.listen
           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 optionals (hasSSL || vhost.rejectSSL) (map (addr: { inherit addr; port = cfg.defaultSSLListenPort; ssl = true; }) addrs)
+              ++ optionals (!onlySSL) (map (addr: { inherit addr; port = cfg.defaultHTTPListenPort; ssl = false; }) addrs);
 
         hostListen =
           if vhost.forceSSL
@@ -275,7 +283,10 @@ let
         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;
@@ -307,7 +318,9 @@ let
           ${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};
@@ -360,7 +373,7 @@ let
       ${optionalString (config.alias != null) "alias ${config.alias};"}
       ${optionalString (config.return != null) "return ${config.return};"}
       ${config.extraConfig}
-      ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
+      ${optionalString (config.proxyPass != null && config.recommendedProxySettings) "include ${recommendedProxyConfig};"}
       ${mkBasicAuth "sublocation" config}
     }
   '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
@@ -380,60 +393,92 @@ let
   );
 
   mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
+
+  snakeOilCert = pkgs.runCommand "nginx-config-validate-cert" { nativeBuildInputs = [ pkgs.openssl.bin ]; } ''
+    mkdir $out
+    openssl genrsa -des3 -passout pass:xxxxx -out server.pass.key 2048
+    openssl rsa -passin pass:xxxxx -in server.pass.key -out $out/server.key
+    openssl req -new -key $out/server.key -out server.csr \
+    -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
+    openssl x509 -req -days 1 -in server.csr -signkey $out/server.key -out $out/server.crt
+  '';
+  validatedConfigFile = pkgs.runCommand "validated-nginx.conf" { nativeBuildInputs = [ cfg.package ]; } ''
+    # nginx absolutely wants to read the certificates even when told to only validate config, so let's provide fake certs
+    sed ${configFile} \
+    -e "s|ssl_certificate .*;|ssl_certificate ${snakeOilCert}/server.crt;|g" \
+    -e "s|ssl_trusted_certificate .*;|ssl_trusted_certificate ${snakeOilCert}/server.crt;|g" \
+    -e "s|ssl_certificate_key .*;|ssl_certificate_key ${snakeOilCert}/server.key;|g" \
+    > conf
+
+    LD_PRELOAD=${pkgs.libredirect}/lib/libredirect.so \
+      NIX_REDIRECTS="/etc/resolv.conf=/dev/null" \
+      nginx -t -c $(readlink -f ./conf) > out 2>&1 || true
+    if ! grep -q "syntax is ok" out; then
+      echo nginx config validation failed.
+      echo config was ${configFile}.
+      echo 'in case of false positive, set `services.nginx.validateConfig` to false.'
+      echo nginx output:
+      cat out
+      exit 1
+    fi
+    cp ${configFile} $out
+  '';
+
+  finalConfigFile = if cfg.validateConfig then validatedConfigFile else configFile;
 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.
-        ";
+        '';
       };
 
       recommendedGzipSettings = mkOption {
         default = false;
         type = types.bool;
-        description = "
+        description = lib.mdDoc ''
           Enable recommended gzip settings.
-        ";
+        '';
       };
 
       recommendedProxySettings = mkOption {
         default = false;
         type = types.bool;
-        description = "
-          Enable recommended proxy settings.
-        ";
+        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.
-        ";
+        '';
       };
 
       defaultListenAddresses = mkOption {
@@ -441,9 +486,27 @@ 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.
-        ";
+        '';
+      };
+
+      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.
+        '';
       };
 
       package = mkOption {
@@ -453,28 +516,37 @@ in
         apply = p: p.override {
           modules = 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`.
+        '';
+      };
+
+      validateConfig = mkOption {
+        default = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform;
+        defaultText = literalExpression "pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform";
+        type = types.bool;
+        description = lib.mdDoc ''
+          Validate the generated nginx config at build time. The check is not very robust and can be disabled in case of false positives. This is notably the case when cross-compiling or when using `include` with files outside of the store.
+        '';
       };
 
       additionalModules = mkOption {
         default = [];
         type = types.listOf (types.attrsOf types.anything);
         example = literalExpression "[ pkgs.nginxModules.brotli ]";
-        description = ''
-          Additional <link xlink:href="https://www.nginx.com/resources/wiki/modules/">third-party nginx modules</link>
+        description = lib.mdDoc ''
+          Additional [third-party nginx modules](https://www.nginx.com/resources/wiki/modules/)
           to install. Packaged modules are available in
-          <literal>pkgs.nginxModules</literal>.
+          `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,47 +557,42 @@ 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.
         '';
       };
 
       appendConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Configuration lines appended to the generated Nginx
           configuration file. Commonly used by different modules
-          providing http snippets. <option>appendConfig</option>
+          providing http snippets. {option}`appendConfig`
           can be specified more than once and it's value will be
-          concatenated (contrary to <option>config</option> which
+          concatenated (contrary to {option}`config` which
           can be set only once).
         '';
       };
@@ -540,7 +607,7 @@ in
                               '"$request" $status $body_bytes_sent '
                               '"$http_referer" "$http_user_agent"';
         '';
-        description = ''
+        description = lib.mdDoc ''
           With nginx you must provide common http context definitions before
           they are used, e.g. log_format, resolver, etc. inside of server
           or location contexts. Use this attribute to set these definitions
@@ -551,12 +618,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,15 +636,15 @@ in
             proxy_pass 192.168.0.1:53535;
           }
         '';
-        description = "
+        description = lib.mdDoc ''
           Configuration lines to be set inside the stream block.
-        ";
+        '';
       };
 
       eventsConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Configuration lines to be set inside the events block.
         '';
       };
@@ -585,72 +652,72 @@ 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`.
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "nginx";
-        description = "User account under which nginx runs.";
+        description = lib.mdDoc "User account under which nginx runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "nginx";
-        description = "Group account under which nginx runs.";
+        description = lib.mdDoc "Group account under which nginx runs.";
       };
 
       serverTokens = mkOption {
         type = types.bool;
         default = false;
-        description = "Show nginx version in headers and error pages.";
+        description = lib.mdDoc "Show nginx version in headers and error pages.";
       };
 
       clientMaxBodySize = mkOption {
         type = types.str;
         default = "10m";
-        description = "Set nginx global client_max_body_size.";
+        description = lib.mdDoc "Set nginx global client_max_body_size.";
       };
 
       sslCiphers = mkOption {
         type = types.nullOr types.str;
         # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
         default = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
-        description = "Ciphers to choose from when negotiating TLS handshakes.";
+        description = lib.mdDoc "Ciphers to choose from when negotiating TLS handshakes.";
       };
 
       sslProtocols = mkOption {
         type = types.str;
         default = "TLSv1.2 TLSv1.3";
         example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3";
-        description = "Allowed TLS protocol versions.";
+        description = lib.mdDoc "Allowed TLS protocol versions.";
       };
 
       sslDhparam = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/path/to/dhparams.pem";
-        description = "Path to DH parameters file.";
+        description = lib.mdDoc "Path to DH parameters file.";
       };
 
       proxyResolveWhileRunning = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Resolves domains of proxyPass targets at runtime
           and not only at start, you have to set
           services.nginx.resolver, too.
@@ -660,7 +727,7 @@ in
       mapHashBucketSize = mkOption {
         type = types.nullOr (types.enum [ 32 64 128 ]);
         default = null;
-        description = ''
+        description = lib.mdDoc ''
             Sets the bucket size for the map variables hash tables. Default
             value depends on the processor’s cache line size.
           '';
@@ -669,7 +736,7 @@ in
       mapHashMaxSize = mkOption {
         type = types.nullOr types.ints.positive;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
             Sets the maximum size of the map variables hash tables.
           '';
       };
@@ -677,7 +744,7 @@ in
       serverNamesHashBucketSize = mkOption {
         type = types.nullOr types.ints.positive;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
             Sets the bucket size for the server names hash tables. Default
             value depends on the processor’s cache line size.
           '';
@@ -686,11 +753,77 @@ in
       serverNamesHashMaxSize = mkOption {
         type = types.nullOr types.ints.positive;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
             Sets the maximum size of the server names hash tables.
           '';
       };
 
+      proxyCache = mkOption {
+        type = types.submodule {
+          options = {
+            enable = mkEnableOption (lib.mdDoc "Enable proxy cache");
+
+            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 proxy cache";
+      };
+
       resolver = mkOption {
         type = types.submodule {
           options = {
@@ -698,13 +831,13 @@ in
               type = types.listOf types.str;
               default = [];
               example = literalExpression ''[ "[::1]" "127.0.0.1:5353" ]'';
-              description = "List of resolvers to use";
+              description = lib.mdDoc "List of resolvers to use";
             };
             valid = mkOption {
               type = types.str;
               default = "";
               example = "30s";
-              description = ''
+              description = lib.mdDoc ''
                 By default, nginx caches answers using the TTL value of a response.
                 An optional valid parameter allows overriding it
               '';
@@ -712,7 +845,7 @@ in
             ipv6 = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
                 If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be
                 specified.
@@ -720,7 +853,7 @@ in
             };
           };
         };
-        description = ''
+        description = lib.mdDoc ''
           Configures name servers used to resolve names of upstream servers into addresses
         '';
         default = {};
@@ -735,14 +868,14 @@ in
                   backup = mkOption {
                     type = types.bool;
                     default = false;
-                    description = ''
+                    description = lib.mdDoc ''
                       Marks the server as a backup server. It will be passed
                       requests when the primary servers are unavailable.
                     '';
                   };
                 };
               });
-              description = ''
+              description = lib.mdDoc ''
                 Defines the address and other parameters of the upstream servers.
               '';
               default = {};
@@ -751,13 +884,13 @@ in
             extraConfig = mkOption {
               type = types.lines;
               default = "";
-              description = ''
+              description = lib.mdDoc ''
                 These lines go to the end of the upstream verbatim.
               '';
             };
           };
         });
-        description = ''
+        description = lib.mdDoc ''
           Defines a group of servers to use as proxy target.
         '';
         default = {};
@@ -789,7 +922,7 @@ in
             };
           };
         '';
-        description = "Declarative vhost config";
+        description = lib.mdDoc "Declarative vhost config";
       };
     };
   };
@@ -932,12 +1065,12 @@ in
         # System Call Filtering
         SystemCallArchitectures = "native";
         SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" ]
-          ++ optionals ((cfg.package != pkgs.tengine) && (!lib.any (mod: (mod.disableIPC or false)) cfg.package.modules)) [ "~@ipc" ];
+          ++ optionals ((cfg.package != pkgs.tengine) && (cfg.package != pkgs.openresty) && (!lib.any (mod: (mod.disableIPC or false)) cfg.package.modules)) [ "~@ipc" ];
       };
     };
 
     environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
-      source = configFile;
+      source = finalConfigFile;
     };
 
     # This service waits for all certificates to be available
@@ -956,7 +1089,7 @@ in
       # certs are updated _after_ config has been reloaded.
       before = sslTargets;
       after = sslServices;
-      restartTriggers = optionals (cfg.enableReload) [ configFile ];
+      restartTriggers = optionals (cfg.enableReload) [ finalConfigFile ];
       # 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/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixos/modules/services/web-servers/nginx/gitweb.nix
index db45577a46d1..ec2c432ca573 100644
--- a/nixos/modules/services/web-servers/nginx/gitweb.nix
+++ b/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -17,7 +17,7 @@ in
     enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         If true, enable gitweb in nginx.
       '';
     };
@@ -25,7 +25,7 @@ in
     location = mkOption {
       default = "/gitweb";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Location to serve gitweb on.
       '';
     };
@@ -33,7 +33,7 @@ in
     user = mkOption {
       default = "nginx";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Existing user that the CGI process will belong to. (Default almost surely will do.)
       '';
     };
@@ -41,15 +41,15 @@ in
     group = mkOption {
       default = "nginx";
       type = types.str;
-      description = ''
-        Group that the CGI process will belong to. (Set to <literal>config.services.gitolite.group</literal> if you are using gitolite.)
+      description = lib.mdDoc ''
+        Group that the CGI process will belong to. (Set to `config.services.gitolite.group` if you are using gitolite.)
       '';
     };
 
     virtualHost = mkOption {
       default = "_";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         VirtualHost to serve gitweb on. Default is catch-all.
       '';
     };
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index 6fd00b386974..2728852058ea 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -3,7 +3,7 @@
 # has additional options that affect the web server as a whole, like
 # the user/group to run under.)
 
-{ lib }:
+{ lib, config }:
 
 with lib;
 
@@ -17,7 +17,7 @@ with lib;
           user = "password";
         };
       '';
-      description = ''
+      description = lib.mdDoc ''
         Basic Auth protection for a vhost.
 
         WARNING: This is implemented to store the password in plain text in the
@@ -28,9 +28,9 @@ with lib;
     basicAuthFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Basic Auth password file for a vhost.
-        Can be created via: <command>htpasswd -c &lt;filename&gt; &lt;username&gt;</command>.
+        Can be created via: {command}`htpasswd -c <filename> <username>`.
 
         WARNING: The generate file contains the users' passwords in a
         non-cryptographically-securely hashed way.
@@ -41,7 +41,7 @@ with lib;
       type = types.nullOr types.str;
       default = null;
       example = "http://www.example.org/";
-      description = ''
+      description = lib.mdDoc ''
         Adds proxy_pass directive and sets recommended proxy headers if
         recommendedProxySettings is enabled.
       '';
@@ -51,7 +51,7 @@ with lib;
       type = types.bool;
       default = false;
       example = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to support proxying websocket connections with HTTP/1.1.
       '';
     };
@@ -60,7 +60,7 @@ with lib;
       type = types.nullOr types.str;
       default = null;
       example = "index.php index.html";
-      description = ''
+      description = lib.mdDoc ''
         Adds index directive.
       '';
     };
@@ -69,7 +69,7 @@ with lib;
       type = types.nullOr types.str;
       default = null;
       example = "$uri =404";
-      description = ''
+      description = lib.mdDoc ''
         Adds try_files directive.
       '';
     };
@@ -78,7 +78,7 @@ with lib;
       type = types.nullOr types.path;
       default = null;
       example = "/your/root/directory";
-      description = ''
+      description = lib.mdDoc ''
         Root directory for requests.
       '';
     };
@@ -87,7 +87,7 @@ with lib;
       type = types.nullOr types.path;
       default = null;
       example = "/your/alias/directory";
-      description = ''
+      description = lib.mdDoc ''
         Alias directory for requests.
       '';
     };
@@ -96,7 +96,7 @@ with lib;
       type = types.nullOr types.str;
       default = null;
       example = "301 http://example.com$request_uri";
-      description = ''
+      description = lib.mdDoc ''
         Adds a return directive, for e.g. redirections.
       '';
     };
@@ -104,7 +104,7 @@ with lib;
     fastcgiParams = mkOption {
       type = types.attrsOf (types.either types.str types.path);
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         FastCGI parameters to override.  Unlike in the Nginx
         configuration file, overriding only some default parameters
         won't unset the default values for other parameters.
@@ -114,7 +114,7 @@ with lib;
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         These lines go to the end of the location verbatim.
       '';
     };
@@ -122,11 +122,20 @@ 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.
       '';
     };
+
+    recommendedProxySettings = mkOption {
+      type = types.bool;
+      default = config.services.nginx.recommendedProxySettings;
+      defaultText = literalExpression "config.services.nginx.recommendedProxySettings";
+      description = lib.mdDoc ''
+        Enable recommended proxy settings.
+      '';
+    };
   };
 }
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 2c77d6ee8162..089decb5f433 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -11,7 +11,7 @@ with lib;
     serverName = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Name of this virtual host. Defaults to attribute name in virtualHosts.
       '';
       example = "example.org";
@@ -21,74 +21,74 @@ with lib;
       type = types.listOf types.str;
       default = [];
       example = [ "www.example.org" "example.org" ];
-      description = ''
+      description = lib.mdDoc ''
         Additional names of virtual hosts served by this virtual host configuration.
       '';
     };
 
     listen = mkOption {
       type = with types; listOf (submodule { options = {
-        addr = mkOption { type = str;  description = "IP address.";  };
-        port = mkOption { type = int;  description = "Port number."; default = 80; };
-        ssl  = mkOption { type = bool; description = "Enable SSL.";  default = false; };
-        extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "backlog=1024" "deferred" ]; };
+        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; };
+        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; }
         { addr = "192.154.1.1"; port = 80; }
       ];
-      description = ''
+      description = lib.mdDoc ''
         Listen addresses and ports for this virtual host.
         IPv6 addresses must be enclosed in square brackets.
-        Note: this option overrides <literal>addSSL</literal>
-        and <literal>onlySSL</literal>.
+        Note: this option overrides `addSSL`
+        and `onlySSL`.
 
         If you only want to set the addresses manually and not
-        the ports, take a look at <literal>listenAddresses</literal>
+        the ports, take a look at `listenAddresses`
       '';
     };
 
     listenAddresses = mkOption {
       type = with types; listOf str;
 
-      description = ''
+      description = lib.mdDoc ''
         Listen addresses for this virtual host.
-        Compared to <literal>listen</literal> 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 <literal>enableIPv6</literal>
+        Note: This option overrides `enableIPv6`
       '';
       default = [];
-      example = [ "127.0.0.1" "::1" ];
+      example = [ "127.0.0.1" "[::1]" ];
     };
 
     enableACME = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to ask Let's Encrypt to sign a certificate for this vhost.
-        Alternately, you can use an existing certificate through <option>useACMEHost</option>.
+        Alternately, you can use an existing certificate through {option}`useACMEHost`.
       '';
     };
 
     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).*
       '';
     };
 
     acmeRoot = mkOption {
       type = types.nullOr types.str;
       default = "/var/lib/acme/acme-challenge";
-      description = ''
-        Directory for the acme challenge which is PUBLIC, don't put certs or keys in here.
+      description = lib.mdDoc ''
+        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.
       '';
     };
@@ -96,18 +96,22 @@ with lib;
     acmeFallbackHost = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
-        Host which to proxy requests to if acme challenge is not found. Useful
+      description = lib.mdDoc ''
+        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.
       '';
     };
 
     addSSL = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
-        <literal>listen</literal> to listen on all interfaces on the respective default
+        `listen` to listen on all interfaces on the respective default
         ports (80, 443).
       '';
     };
@@ -115,9 +119,9 @@ with lib;
     onlySSL = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable HTTPS and reject plain HTTP connections. This will set
-        defaults for <literal>listen</literal> to listen on all interfaces on port 443.
+        defaults for `listen` to listen on all interfaces on port 443.
       '';
     };
 
@@ -130,10 +134,10 @@ with lib;
     forceSSL = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to add a separate nginx server block that permanently redirects (301)
         all plain HTTP traffic to HTTPS. This will set defaults for
-        <literal>listen</literal> to listen on all interfaces on the respective default
+        `listen` to listen on all interfaces on the respective default
         ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
       '';
     };
@@ -141,11 +145,11 @@ with lib;
     rejectSSL = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to listen for and reject all HTTPS connections to this vhost. Useful in
-        <link linkend="opt-services.nginx.virtualHosts._name_.default">default</link>
+        [default](#opt-services.nginx.virtualHosts._name_.default)
         server blocks to avoid serving the certificate for another vhost. Uses the
-        <literal>ssl_reject_handshake</literal> directive available in nginx versions
+        `ssl_reject_handshake` directive available in nginx versions
         1.19.4 and above.
       '';
     };
@@ -153,7 +157,7 @@ with lib;
     kTLS = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable kTLS support.
         Implementing TLS in the kernel (kTLS) improves performance by significantly
         reducing the need for copying operations between user space and the kernel.
@@ -164,26 +168,26 @@ with lib;
     sslCertificate = mkOption {
       type = types.path;
       example = "/var/host.cert";
-      description = "Path to server SSL certificate.";
+      description = lib.mdDoc "Path to server SSL certificate.";
     };
 
     sslCertificateKey = mkOption {
       type = types.path;
       example = "/var/host.key";
-      description = "Path to server SSL certificate key.";
+      description = lib.mdDoc "Path to server SSL certificate key.";
     };
 
     sslTrustedCertificate = mkOption {
       type = types.nullOr types.path;
       default = null;
       example = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
-      description = "Path to root SSL certificate for stapling and client certificates.";
+      description = lib.mdDoc "Path to root SSL certificate for stapling and client certificates.";
     };
 
     http2 = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable HTTP 2.
         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
@@ -197,10 +201,10 @@ with lib;
     http3 = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         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>.
+        This requires using `pkgs.nginxQuic` package
+        which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`.
         Note that HTTP 3 support is experimental and
         *not* yet recommended for production.
         Read more at https://quic.nginx.org/
@@ -210,7 +214,7 @@ with lib;
     reuseport = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Create an individual listening socket .
         It is required to specify only once on one of the hosts.
       '';
@@ -220,7 +224,7 @@ with lib;
       type = types.nullOr types.path;
       default = null;
       example = "/data/webserver/docs";
-      description = ''
+      description = lib.mdDoc ''
         The path of the web root directory.
       '';
     };
@@ -228,7 +232,7 @@ with lib;
     default = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Makes this vhost the default.
       '';
     };
@@ -236,7 +240,7 @@ with lib;
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         These lines go to the end of the vhost verbatim.
       '';
     };
@@ -245,7 +249,7 @@ with lib;
       type = types.nullOr types.str;
       default = null;
       example = "newserver.example.org";
-      description = ''
+      description = lib.mdDoc ''
         If set, all requests for this host are redirected permanently to
         the given hostname.
       '';
@@ -259,7 +263,7 @@ with lib;
           user = "password";
         };
       '';
-      description = ''
+      description = lib.mdDoc ''
         Basic Auth protection for a vhost.
 
         WARNING: This is implemented to store the password in plain text in the
@@ -270,9 +274,9 @@ with lib;
     basicAuthFile = mkOption {
       type = types.nullOr types.path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Basic Auth password file for a vhost.
-        Can be created via: <command>htpasswd -c &lt;filename&gt; &lt;username&gt;</command>.
+        Can be created via: {command}`htpasswd -c <filename> <username>`.
 
         WARNING: The generate file contains the users' passwords in a
         non-cryptographically-securely hashed way.
@@ -281,7 +285,7 @@ with lib;
 
     locations = mkOption {
       type = types.attrsOf (types.submodule (import ./location-options.nix {
-        inherit lib;
+        inherit lib config;
       }));
       default = {};
       example = literalExpression ''
@@ -291,7 +295,7 @@ with lib;
           };
         };
       '';
-      description = "Declarative location config";
+      description = lib.mdDoc "Declarative location config";
     };
   };
 }
diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix
index 87c68fa074a1..0bd1d5b29b31 100644
--- a/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/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";
         };
@@ -51,7 +54,7 @@ let
           type = types.str;
           default = "";
           example = "/path/to/unix/socket";
-          description = ''
+          description = lib.mdDoc ''
             The address on which to accept FastCGI requests.
           '';
         };
@@ -60,22 +63,22 @@ let
           type = types.package;
           default = cfg.phpPackage;
           defaultText = literalExpression "config.services.phpfpm.phpPackage";
-          description = ''
+          description = lib.mdDoc ''
             The PHP package to use for running this PHP-FPM pool.
           '';
         };
 
         phpOptions = mkOption {
           type = types.lines;
-          description = ''
-            "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
+          description = lib.mdDoc ''
+            "Options appended to the PHP configuration file {file}`php.ini` used for this PHP-FPM pool."
           '';
         };
 
         phpEnv = lib.mkOption {
           type = with types; attrsOf str;
           default = {};
-          description = ''
+          description = lib.mdDoc ''
             Environment variables used for this PHP-FPM pool.
           '';
           example = literalExpression ''
@@ -90,22 +93,22 @@ let
 
         user = mkOption {
           type = types.str;
-          description = "User account under which this pool runs.";
+          description = lib.mdDoc "User account under which this pool runs.";
         };
 
         group = mkOption {
           type = types.str;
-          description = "Group account under which this pool runs.";
+          description = lib.mdDoc "Group account under which this pool runs.";
         };
 
         settings = mkOption {
           type = with types; attrsOf (oneOf [ str int bool ]);
           default = {};
-          description = ''
+          description = lib.mdDoc ''
             PHP-FPM pool directives. Refer to the "List of pool directives" section of
-            <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+            <https://www.php.net/manual/en/install.fpm.configuration.php>
             for details. Note that settings names must be enclosed in quotes (e.g.
-            <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+            `"pm.max_children"` instead of `pm.max_children`).
           '';
           example = literalExpression ''
             {
@@ -122,9 +125,9 @@ let
         extraConfig = mkOption {
           type = with types; nullOr lines;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Extra lines that go into the pool configuration.
-            See the documentation on <literal>php-fpm.conf</literal> for
+            See the documentation on `php-fpm.conf` for
             details on configuration directives.
           '';
         };
@@ -154,24 +157,24 @@ in {
       settings = mkOption {
         type = with types; attrsOf (oneOf [ str int bool ]);
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of
-          <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+          <https://www.php.net/manual/en/install.fpm.configuration.php>
           for details. Note that settings names must be enclosed in quotes (e.g.
-          <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
-          You need not specify the options <literal>error_log</literal> or
-          <literal>daemonize</literal> here, since they are generated by NixOS.
+          `"pm.max_children"` instead of `pm.max_children`).
+          You need not specify the options `error_log` or
+          `daemonize` here, since they are generated by NixOS.
         '';
       };
 
       extraConfig = mkOption {
         type = with types; nullOr lines;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration that should be put in the global section of
           the PHP-FPM configuration file. Do not specify the options
-          <literal>error_log</literal> or
-          <literal>daemonize</literal> here, since they are generated by
+          `error_log` or
+          `daemonize` here, since they are generated by
           NixOS.
         '';
       };
@@ -180,7 +183,7 @@ in {
         type = types.package;
         default = pkgs.php;
         defaultText = literalExpression "pkgs.php";
-        description = ''
+        description = lib.mdDoc ''
           The PHP package to use for running the PHP-FPM service.
         '';
       };
@@ -192,8 +195,8 @@ in {
           ''
             date.timezone = "CET"
           '';
-        description = ''
-          Options appended to the PHP configuration file <filename>php.ini</filename>.
+        description = lib.mdDoc ''
+          Options appended to the PHP configuration file {file}`php.ini`.
         '';
       };
 
@@ -216,7 +219,7 @@ in {
              };
            }
          }'';
-        description = ''
+        description = lib.mdDoc ''
           PHP-FPM pools. If no pools are defined, the PHP-FPM
           service is disabled.
         '';
diff --git a/nixos/modules/services/web-servers/pomerium.nix b/nixos/modules/services/web-servers/pomerium.nix
index 0b460755f50e..90748f74d24e 100644
--- a/nixos/modules/services/web-servers/pomerium.nix
+++ b/nixos/modules/services/web-servers/pomerium.nix
@@ -7,18 +7,18 @@ 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;
       default = null;
-      description = "Path to Pomerium config YAML. If set, overrides services.pomerium.settings.";
+      description = lib.mdDoc "Path to Pomerium config YAML. If set, overrides services.pomerium.settings.";
     };
 
     useACMEHost = mkOption {
       type = with types; nullOr str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         If set, use a NixOS-generated ACME certificate with the specified name.
 
         Note that this will require you to use a non-HTTP-based challenge, or
@@ -32,13 +32,13 @@ in
     };
 
     settings = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         The contents of Pomerium's config.yaml, in Nix expressions.
 
         Specifying configFile will override this in its entirety.
 
-        See <link xlink:href="https://pomerium.io/reference/">the Pomerium
-        configuration reference</link> for more information about what to put
+        See [the Pomerium
+        configuration reference](https://pomerium.io/reference/) for more information about what to put
         here.
       '';
       default = {};
@@ -48,7 +48,7 @@ in
     secretsFile = mkOption {
       type = with types; nullOr path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Path to file containing secrets for Pomerium, in systemd
         EnvironmentFile format. See the systemd.exec(5) man page.
       '';
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index 877097cf3781..d8bfee547c79 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -19,14 +19,14 @@ in
   options = {
 
     services.tomcat = {
-      enable = mkEnableOption "Apache Tomcat";
+      enable = mkEnableOption (lib.mdDoc "Apache Tomcat");
 
       package = mkOption {
         type = types.package;
         default = pkgs.tomcat9;
         defaultText = literalExpression "pkgs.tomcat9";
         example = lib.literalExpression "pkgs.tomcat9";
-        description = ''
+        description = lib.mdDoc ''
           Which tomcat package to use.
         '';
       };
@@ -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.
@@ -56,79 +56,79 @@ in
       logDirs = mkOption {
         default = [];
         type = types.listOf types.path;
-        description = "Directories to create in baseDir/logs/";
+        description = lib.mdDoc "Directories to create in baseDir/logs/";
       };
 
       extraConfigFiles = mkOption {
         default = [];
         type = types.listOf types.path;
-        description = "Extra configuration files to pull into the tomcat conf directory";
+        description = lib.mdDoc "Extra configuration files to pull into the tomcat conf directory";
       };
 
       extraEnvironment = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "ENVIRONMENT=production" ];
-        description = "Environment Variables to pass to the tomcat service";
+        description = lib.mdDoc "Environment Variables to pass to the tomcat service";
       };
 
       extraGroups = mkOption {
         default = [];
         type = types.listOf types.str;
         example = [ "users" ];
-        description = "Defines extra groups to which the tomcat user belongs.";
+        description = lib.mdDoc "Defines extra groups to which the tomcat user belongs.";
       };
 
       user = mkOption {
         type = types.str;
         default = "tomcat";
-        description = "User account under which Apache Tomcat runs.";
+        description = lib.mdDoc "User account under which Apache Tomcat runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "tomcat";
-        description = "Group account under which Apache Tomcat runs.";
+        description = lib.mdDoc "Group account under which Apache Tomcat runs.";
       };
 
       javaOpts = mkOption {
         type = types.either (types.listOf types.str) types.str;
         default = "";
-        description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
+        description = lib.mdDoc "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
       };
 
       catalinaOpts = mkOption {
         type = types.either (types.listOf types.str) types.str;
         default = "";
-        description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
+        description = lib.mdDoc "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
       };
 
       sharedLibs = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
+        description = lib.mdDoc "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
       };
 
       serverXml = mkOption {
         type = types.lines;
         default = "";
-        description = "
+        description = lib.mdDoc ''
           Verbatim server.xml configuration.
           This is mutually exclusive with the virtualHosts options.
-        ";
+        '';
       };
 
       commonLibs = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
+        description = lib.mdDoc "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
       };
 
       webapps = mkOption {
         type = types.listOf types.path;
         default = [ tomcat.webapps ];
         defaultText = literalExpression "[ config.services.tomcat.package.webapps ]";
-        description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
+        description = lib.mdDoc "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
       };
 
       virtualHosts = mkOption {
@@ -136,16 +136,16 @@ in
           options = {
             name = mkOption {
               type = types.str;
-              description = "name of the virtualhost";
+              description = lib.mdDoc "name of the virtualhost";
             };
             aliases = mkOption {
               type = types.listOf types.str;
-              description = "aliases of the virtualhost";
+              description = lib.mdDoc "aliases of the virtualhost";
               default = [];
             };
             webapps = mkOption {
               type = types.listOf types.path;
-              description = ''
+              description = lib.mdDoc ''
                 List containing web application WAR files and/or directories containing
                 web applications and configuration files for the virtual host.
               '';
@@ -154,20 +154,20 @@ in
           };
         });
         default = [];
-        description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
+        description = lib.mdDoc "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
       };
 
       logPerVirtualHost = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable logging per virtual host.";
+        description = lib.mdDoc "Whether to enable logging per virtual host.";
       };
 
       jdk = mkOption {
         type = types.package;
         default = pkgs.jdk;
         defaultText = literalExpression "pkgs.jdk";
-        description = "Which JDK to use.";
+        description = lib.mdDoc "Which JDK to use.";
       };
 
       axis2 = {
@@ -175,13 +175,13 @@ in
         enable = mkOption {
           default = false;
           type = types.bool;
-          description = "Whether to enable an Apache Axis2 container";
+          description = lib.mdDoc "Whether to enable an Apache Axis2 container";
         };
 
         services = mkOption {
           default = [];
           type = types.listOf types.str;
-          description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
+          description = lib.mdDoc "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
         };
 
       };
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix
index eb7fd0995de0..9e5603e0edc3 100644
--- a/nixos/modules/services/web-servers/traefik.nix
+++ b/nixos/modules/services/web-servers/traefik.nix
@@ -50,20 +50,20 @@ let
     cfg.staticConfigFile;
 in {
   options.services.traefik = {
-    enable = mkEnableOption "Traefik web server";
+    enable = mkEnableOption (lib.mdDoc "Traefik web server");
 
     staticConfigFile = mkOption {
       default = null;
       example = literalExpression "/path/to/static_config.toml";
       type = types.nullOr types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to traefik's static configuration to use.
-        (Using that option has precedence over <literal>staticConfigOptions</literal> and <literal>dynamicConfigOptions</literal>)
+        (Using that option has precedence over `staticConfigOptions` and `dynamicConfigOptions`)
       '';
     };
 
     staticConfigOptions = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Static configuration for Traefik.
       '';
       type = jsonValue;
@@ -80,14 +80,14 @@ in {
       default = null;
       example = literalExpression "/path/to/dynamic_config.toml";
       type = types.nullOr types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to traefik's dynamic configuration to use.
-        (Using that option has precedence over <literal>dynamicConfigOptions</literal>)
+        (Using that option has precedence over `dynamicConfigOptions`)
       '';
     };
 
     dynamicConfigOptions = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Dynamic configuration for Traefik.
       '';
       type = jsonValue;
@@ -106,7 +106,7 @@ in {
     dataDir = mkOption {
       default = "/var/lib/traefik";
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Location for any persistent data traefik creates, ie. acme
       '';
     };
@@ -115,9 +115,9 @@ in {
       default = "traefik";
       type = types.str;
       example = "docker";
-      description = ''
+      description = lib.mdDoc ''
         Set the group that traefik runs under.
-        For the docker backend this needs to be set to <literal>docker</literal> instead.
+        For the docker backend this needs to be set to `docker` instead.
       '';
     };
 
@@ -125,7 +125,7 @@ in {
       default = pkgs.traefik;
       defaultText = literalExpression "pkgs.traefik";
       type = types.package;
-      description = "Traefik package to use.";
+      description = lib.mdDoc "Traefik package to use.";
     };
   };
 
diff --git a/nixos/modules/services/web-servers/trafficserver/default.nix b/nixos/modules/services/web-servers/trafficserver/default.nix
index b52087fa038c..17dece8746a1 100644
--- a/nixos/modules/services/web-servers/trafficserver/default.nix
+++ b/nixos/modules/services/web-servers/trafficserver/default.nix
@@ -33,17 +33,17 @@ let
 in
 {
   options.services.trafficserver = {
-    enable = mkEnableOption "Apache Traffic Server";
+    enable = mkEnableOption (lib.mdDoc "Apache Traffic Server");
 
     cache = mkOption {
       type = types.lines;
       default = "";
       example = "dest_domain=example.com suffix=js action=never-cache";
-      description = ''
+      description = lib.mdDoc ''
         Caching rules that overrule the origin's caching policy.
 
-        Consult the <link xlink:href="${getManualUrl "cache.config"}">upstream
-        documentation</link> for more details.
+        Consult the [upstream
+        documentation](${getManualUrl "cache.config"}) for more details.
       '';
     };
 
@@ -51,18 +51,18 @@ in
       type = types.lines;
       default = "";
       example = "domain=example.com volume=1";
-      description = ''
+      description = lib.mdDoc ''
         Partition the cache according to origin server or domain
 
-        Consult the <link xlink:href="${getManualUrl "hosting.config"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "hosting.config"}) for more details.
       '';
     };
 
     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 = [{
@@ -73,25 +73,25 @@ in
           }];
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Control client access to Traffic Server and Traffic Server connections
         to upstream servers.
 
-        Consult the <link xlink:href="${getManualUrl "ip_allow.yaml"}">upstream
-        documentation</link> for more details.
+        Consult the [upstream
+        documentation](${getManualUrl "ip_allow.yaml"}) for more details.
       '';
     };
 
     logging = mkOption {
       type = types.nullOr yaml.type;
       default = lib.importJSON ./logging.json;
-      defaultText = literalDocBook "upstream defaults";
+      defaultText = literalMD "upstream defaults";
       example = { };
-      description = ''
+      description = lib.mdDoc ''
         Configure logs.
 
-        Consult the <link xlink:href="${getManualUrl "logging.yaml"}">upstream
-        documentation</link> for more details.
+        Consult the [upstream
+        documentation](${getManualUrl "logging.yaml"}) for more details.
       '';
     };
 
@@ -101,23 +101,23 @@ in
       example = ''
         dest_domain=. method=get parent="p1.example:8080; p2.example:8080" round_robin=true
       '';
-      description = ''
+      description = lib.mdDoc ''
         Identify the parent proxies used in an cache hierarchy.
 
-        Consult the <link xlink:href="${getManualUrl "parent.config"}">upstream
-        documentation</link> for more details.
+        Consult the [upstream
+        documentation](${getManualUrl "parent.config"}) for more details.
       '';
     };
 
     plugins = mkOption {
       default = [ ];
 
-      description = ''
+      description = lib.mdDoc ''
         Controls run-time loadable plugins available to Traffic Server, as
         well as their configuration.
 
-        Consult the <link xlink:href="${getManualUrl "plugin.config"}">upstream
-        documentation</link> for more details.
+        Consult the [upstream
+        documentation](${getManualUrl "plugin.config"}) for more details.
       '';
 
       type = with types;
@@ -125,7 +125,7 @@ in
           options.path = mkOption {
             type = str;
             example = "xdebug.so";
-            description = ''
+            description = lib.mdDoc ''
               Path to plugin. The path can either be absolute, or relative to
               the plugin directory.
             '';
@@ -134,7 +134,7 @@ in
             type = str;
             default = "";
             example = "--header=ATS-My-Debug";
-            description = "arguments to pass to the plugin";
+            description = lib.mdDoc "arguments to pass to the plugin";
           };
         });
     };
@@ -148,11 +148,11 @@ in
         valueType;
       default = { };
       example = { proxy.config.proxy_name = "my_server"; };
-      description = ''
+      description = lib.mdDoc ''
         List of configurable variables used by Traffic Server.
 
-        Consult the <link xlink:href="${getManualUrl "records.config"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "records.config"}) for more details.
       '';
     };
 
@@ -160,11 +160,11 @@ in
       type = types.lines;
       default = "";
       example = "map http://from.example http://origin.example";
-      description = ''
+      description = lib.mdDoc ''
         URL remapping rules used by Traffic Server.
 
-        Consult the <link xlink:href="${getManualUrl "remap.config"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "remap.config"}) for more details.
       '';
     };
 
@@ -175,12 +175,12 @@ in
         dest_domain=internal.corp.example named="255.255.255.255:212 255.255.255.254" def_domain=corp.example search_list="corp.example corp1.example"
         dest_domain=!internal.corp.example named=255.255.255.253
       '';
-      description = ''
+      description = lib.mdDoc ''
         Specify the DNS server that Traffic Server should use under specific
         conditions.
 
-        Consult the <link xlink:href="${getManualUrl "splitdns.config"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "splitdns.config"}) for more details.
       '';
     };
 
@@ -188,11 +188,11 @@ in
       type = types.lines;
       default = "";
       example = "dest_ip=* ssl_cert_name=default.pem";
-      description = ''
+      description = lib.mdDoc ''
         Configure SSL server certificates to terminate the SSL sessions.
 
-        Consult the <link xlink:href="${getManualUrl "ssl_multicert.config"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "ssl_multicert.config"}) for more details.
       '';
     };
 
@@ -207,12 +207,12 @@ in
           }];
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Configure aspects of TLS connection handling for both inbound and
         outbound connections.
 
-        Consult the <link xlink:href="${getManualUrl "sni.yaml"}">upstream
-        documentation</link> for more details.
+        Consult the [upstream
+        documentation](${getManualUrl "sni.yaml"}) for more details.
       '';
     };
 
@@ -220,23 +220,23 @@ in
       type = types.lines;
       default = "/var/cache/trafficserver 256M";
       example = "/dev/disk/by-id/XXXXX volume=1";
-      description = ''
+      description = lib.mdDoc ''
         List all the storage that make up the Traffic Server cache.
 
-        Consult the <link xlink:href="${getManualUrl "storage.config"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "storage.config"}) for more details.
       '';
     };
 
     strategies = mkOption {
       type = types.nullOr yaml.type;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Specify the next hop proxies used in an cache hierarchy and the
         algorithms used to select the next proxy.
 
-        Consult the <link xlink:href="${getManualUrl "strategies.yaml"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "strategies.yaml"}) for more details.
       '';
     };
 
@@ -244,12 +244,12 @@ in
       type = types.nullOr yaml.type;
       default = "";
       example = "volume=1 scheme=http size=20%";
-      description = ''
+      description = lib.mdDoc ''
         Manage cache space more efficiently and restrict disk usage by
         creating cache volumes of different sizes.
 
-        Consult the <link xlink:href="${getManualUrl "volume.config"}">
-        upstream documentation</link> for more details.
+        Consult the [
+        upstream documentation](${getManualUrl "volume.config"}) for more details.
       '';
     };
   };
diff --git a/nixos/modules/services/web-servers/ttyd.nix b/nixos/modules/services/web-servers/ttyd.nix
index 431509f7fd56..e0a8b5179e06 100644
--- a/nixos/modules/services/web-servers/ttyd.nix
+++ b/nixos/modules/services/web-servers/ttyd.nix
@@ -30,49 +30,49 @@ in
 
   options = {
     services.ttyd = {
-      enable = mkEnableOption "ttyd daemon";
+      enable = mkEnableOption (lib.mdDoc "ttyd daemon");
 
       port = mkOption {
         type = types.port;
         default = 7681;
-        description = "Port to listen on (use 0 for random port)";
+        description = lib.mdDoc "Port to listen on (use 0 for random port)";
       };
 
       socket = mkOption {
         type = types.nullOr types.path;
         default = null;
         example = "/var/run/ttyd.sock";
-        description = "UNIX domain socket path to bind.";
+        description = lib.mdDoc "UNIX domain socket path to bind.";
       };
 
       interface = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "eth0";
-        description = "Network interface to bind.";
+        description = lib.mdDoc "Network interface to bind.";
       };
 
       username = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "Username for basic authentication.";
+        description = lib.mdDoc "Username for basic authentication.";
       };
 
       passwordFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         apply = value: if value == null then null else toString value;
-        description = ''
+        description = lib.mdDoc ''
           File containing the password to use for basic authentication.
           For insecurely putting the password in the globally readable store use
-          <literal>pkgs.writeText "ttydpw" "MyPassword"</literal>.
+          `pkgs.writeText "ttydpw" "MyPassword"`.
         '';
       };
 
       signal = mkOption {
         type = types.ints.u8;
         default = 1;
-        description = "Signal to send to the command on session close.";
+        description = lib.mdDoc "Signal to send to the command on session close.";
       };
 
       clientOptions = mkOption {
@@ -83,75 +83,75 @@ in
           fontFamily = "Fira Code";
 
         }'';
-        description = ''
+        description = lib.mdDoc ''
           Attribute set of client options for xtermjs.
-          <link xlink:href="https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/"/>
+          <https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/>
         '';
       };
 
       terminalType = mkOption {
         type = types.str;
         default = "xterm-256color";
-        description = "Terminal type to report.";
+        description = lib.mdDoc "Terminal type to report.";
       };
 
       checkOrigin = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to allow a websocket connection from a different origin.";
+        description = lib.mdDoc "Whether to allow a websocket connection from a different origin.";
       };
 
       maxClients = mkOption {
         type = types.int;
         default = 0;
-        description = "Maximum clients to support (0, no limit)";
+        description = lib.mdDoc "Maximum clients to support (0, no limit)";
       };
 
       indexFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "Custom index.html path";
+        description = lib.mdDoc "Custom index.html path";
       };
 
       enableIPv6 = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether or not to enable IPv6 support.";
+        description = lib.mdDoc "Whether or not to enable IPv6 support.";
       };
 
       enableSSL = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether or not to enable SSL (https) support.";
+        description = lib.mdDoc "Whether or not to enable SSL (https) support.";
       };
 
       certFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "SSL certificate file path.";
+        description = lib.mdDoc "SSL certificate file path.";
       };
 
       keyFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         apply = value: if value == null then null else toString value;
-        description = ''
+        description = lib.mdDoc ''
           SSL key file path.
           For insecurely putting the keyFile in the globally readable store use
-          <literal>pkgs.writeText "ttydKeyFile" "SSLKEY"</literal>.
+          `pkgs.writeText "ttydKeyFile" "SSLKEY"`.
         '';
       };
 
       caFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = "SSL CA file path for client certificate verification.";
+        description = lib.mdDoc "SSL CA file path for client certificate verification.";
       };
 
       logLevel = mkOption {
         type = types.int;
         default = 7;
-        description = "Set log level.";
+        description = lib.mdDoc "Set log level.";
       };
     };
   };
@@ -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/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index b2eecdbb53ee..0aaac8a14e49 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -10,32 +10,32 @@ 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;
         defaultText = literalExpression "pkgs.unit";
-        description = "Unit package to use.";
+        description = lib.mdDoc "Unit package to use.";
       };
       user = mkOption {
         type = types.str;
         default = "unit";
-        description = "User account under which unit runs.";
+        description = lib.mdDoc "User account under which unit runs.";
       };
       group = mkOption {
         type = types.str;
         default = "unit";
-        description = "Group account under which unit runs.";
+        description = lib.mdDoc "Group account under which unit runs.";
       };
       stateDir = mkOption {
         type = types.path;
         default = "/var/spool/unit";
-        description = "Unit data directory.";
+        description = lib.mdDoc "Unit data directory.";
       };
       logDir = mkOption {
         type = types.path;
         default = "/var/log/unit";
-        description = "Unit log directory.";
+        description = lib.mdDoc "Unit log directory.";
       };
       config = mkOption {
         type = types.str;
@@ -75,7 +75,7 @@ in {
             }
           }
         '';
-        description = "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration";
+        description = lib.mdDoc "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration";
       };
     };
   };
diff --git a/nixos/modules/services/web-servers/uwsgi.nix b/nixos/modules/services/web-servers/uwsgi.nix
index 1b3474f2f521..510582feaae1 100644
--- a/nixos/modules/services/web-servers/uwsgi.nix
+++ b/nixos/modules/services/web-servers/uwsgi.nix
@@ -75,13 +75,13 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable uWSGI";
+        description = lib.mdDoc "Enable uWSGI";
       };
 
       runDir = mkOption {
         type = types.path;
         default = "/run/uwsgi";
-        description = "Where uWSGI communication sockets can live";
+        description = lib.mdDoc "Where uWSGI communication sockets can live";
       };
 
       package = mkOption {
@@ -124,37 +124,37 @@ in {
             };
           }
         '';
-        description = ''
-          uWSGI configuration. It awaits an attribute <literal>type</literal> inside which can be either
-          <literal>normal</literal> or <literal>emperor</literal>.
+        description = lib.mdDoc ''
+          uWSGI configuration. It awaits an attribute `type` inside which can be either
+          `normal` or `emperor`.
 
-          For <literal>normal</literal> mode you can specify <literal>pythonPackages</literal> as a function
-          from libraries set into a list of libraries. <literal>pythonpath</literal> will be set accordingly.
+          For `normal` mode you can specify `pythonPackages` as a function
+          from libraries set into a list of libraries. `pythonpath` will be set accordingly.
 
-          For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute
+          For `emperor` mode, you should use `vassals` attribute
           which should be either a set of names and configurations or a path to a directory.
 
           Other attributes will be used in configuration file as-is. Notice that you can redefine
-          <literal>plugins</literal> setting here.
+          `plugins` setting here.
         '';
       };
 
       plugins = mkOption {
         type = types.listOf types.str;
         default = [];
-        description = "Plugins used with uWSGI";
+        description = lib.mdDoc "Plugins used with uWSGI";
       };
 
       user = mkOption {
         type = types.str;
         default = "uwsgi";
-        description = "User account under which uWSGI runs.";
+        description = lib.mdDoc "User account under which uWSGI runs.";
       };
 
       group = mkOption {
         type = types.str;
         default = "uwsgi";
-        description = "Group account under which uWSGI runs.";
+        description = lib.mdDoc "Group account under which uWSGI runs.";
       };
 
       capabilities = mkOption {
@@ -167,23 +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/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix
index fe817313a993..e34c22d2868f 100644
--- a/nixos/modules/services/web-servers/varnish/default.nix
+++ b/nixos/modules/services/web-servers/varnish/default.nix
@@ -11,15 +11,15 @@ 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;
         default = pkgs.varnish;
         defaultText = literalExpression "pkgs.varnish";
-        description = ''
+        description = lib.mdDoc ''
           The package to use
         '';
       };
@@ -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)
-        ";
+        '';
       };
     };
 
diff --git a/nixos/modules/services/web-servers/zope2.nix b/nixos/modules/services/web-servers/zope2.nix
index 922109160228..a17fe6bc2082 100644
--- a/nixos/modules/services/web-servers/zope2.nix
+++ b/nixos/modules/services/web-servers/zope2.nix
@@ -12,31 +12,31 @@ let
       name = mkOption {
         default = "${name}";
         type = types.str;
-        description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
+        description = lib.mdDoc "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
       };
 
       threads = mkOption {
         default = 2;
         type = types.int;
-        description = "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
+        description = lib.mdDoc "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
       };
 
       http_address = mkOption {
         default = "localhost:8080";
         type = types.str;
-        description = "Give a port and address for the HTTP server.";
+        description = lib.mdDoc "Give a port and address for the HTTP server.";
       };
 
       user = mkOption {
         default = "zope2";
         type = types.str;
-        description = "The name of the effective user for the Zope process.";
+        description = lib.mdDoc "The name of the effective user for the Zope process.";
       };
 
       clientHome = mkOption {
         default = "/var/lib/zope2/${name}";
         type = types.path;
-        description = "Home directory of zope2 instance.";
+        description = lib.mdDoc "Home directory of zope2 instance.";
       };
       extra = mkOption {
         default =
@@ -53,12 +53,12 @@ let
           </zodb_db>
           '';
         type = types.lines;
-        description = "Extra zope.conf";
+        description = lib.mdDoc "Extra zope.conf";
       };
 
       packages = mkOption {
         type = types.listOf types.package;
-        description = "The list of packages you want to make available to the zope2 instance.";
+        description = lib.mdDoc "The list of packages you want to make available to the zope2 instance.";
       };
 
     };
@@ -95,7 +95,7 @@ in
           };
         }
       '';
-      description = "zope2 instances to be created automaticaly by the system.";
+      description = lib.mdDoc "zope2 instances to be created automatically by the system.";
     };
   };
 
diff --git a/nixos/modules/services/x11/clight.nix b/nixos/modules/services/x11/clight.nix
index d994a658cbaa..0f66e191fe28 100644
--- a/nixos/modules/services/x11/clight.nix
+++ b/nixos/modules/services/x11/clight.nix
@@ -28,29 +28,23 @@ let
       cfg.settings));
 in {
   options.services.clight = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to enable clight or not.
-      '';
-    };
+    enable = mkEnableOption (lib.mdDoc "clight");
 
     temperature = {
       day = mkOption {
         type = types.int;
         default = 5500;
-        description = ''
+        description = lib.mdDoc ''
           Colour temperature to use during the day, between
-          <literal>1000</literal> and <literal>25000</literal> K.
+          `1000` and `25000` K.
         '';
       };
       night = mkOption {
         type = types.int;
         default = 3700;
-        description = ''
+        description = lib.mdDoc ''
           Colour temperature to use at night, between
-          <literal>1000</literal> and <literal>25000</literal> K.
+          `1000` and `25000` K.
         '';
       };
     };
@@ -62,9 +56,9 @@ in {
       type = with types; attrsOf (nullOr (either collectionTypes (attrsOf collectionTypes)));
       default = {};
       example = { captures = 20; gamma_long_transition = true; ac_capture_timeouts = [ 120 300 60 ]; };
-      description = ''
+      description = lib.mdDoc ''
         Additional configuration to extend clight.conf. See
-        <link xlink:href="https://github.com/FedeDP/Clight/blob/master/Extra/clight.conf"/> for a
+        <https://github.com/FedeDP/Clight/blob/master/Extra/clight.conf> for a
         sample configuration file.
       '';
     };
diff --git a/nixos/modules/services/x11/colord.nix b/nixos/modules/services/x11/colord.nix
index 31ccee6aa33f..cb7b9096e5db 100644
--- a/nixos/modules/services/x11/colord.nix
+++ b/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/nixos/modules/services/x11/desktop-managers/cde.nix b/nixos/modules/services/x11/desktop-managers/cde.nix
index 6c7105729cfd..e0b4fb0e7bfb 100644
--- a/nixos/modules/services/x11/desktop-managers/cde.nix
+++ b/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;
@@ -19,7 +19,7 @@ in {
           xclock bitmap xlsfonts xfd xrefresh xload xwininfo xdpyinfo xwd xwud
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra packages to be installed system wide.
       '';
     };
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index 705dbec5e742..08c5625fc7dd 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -12,22 +12,23 @@ 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 = [];
         type = types.listOf types.package;
         example = literalExpression "[ pkgs.gnome.gpaste ]";
-        description = ''
+        description = lib.mdDoc ''
           Additional list of packages to be added to the session search path.
           Useful for GSettings-conditional autostart.
 
@@ -38,13 +39,13 @@ in
       extraGSettingsOverrides = mkOption {
         default = "";
         type = types.lines;
-        description = "Additional gsettings overrides.";
+        description = lib.mdDoc "Additional gsettings overrides.";
       };
 
       extraGSettingsOverridePackages = mkOption {
         default = [];
         type = types.listOf types.path;
-        description = "List of packages for which gsettings are overridden.";
+        description = lib.mdDoc "List of packages for which gsettings are overridden.";
       };
     };
 
@@ -52,19 +53,32 @@ in
       default = [];
       example = literalExpression "[ pkgs.cinnamon.blueberry ]";
       type = types.listOf types.package;
-      description = "Which packages cinnamon should exclude from the default environment";
+      description = lib.mdDoc "Which packages cinnamon should exclude from the default environment";
     };
 
   };
 
   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-x-icons) {
+          name = mkDefault "Mint-Y-Aqua";
+          package = mkDefault pkgs.cinnamon.mint-x-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
@@ -82,6 +96,7 @@ in
       '';
 
       # Default services
+      services.blueman.enable = mkDefault true;
       hardware.bluetooth.enable = mkDefault true;
       hardware.pulseaudio.enable = mkDefault true;
       security.polkit.enable = true;
@@ -90,8 +105,8 @@ in
       services.dbus.packages = with pkgs.cinnamon; [
         cinnamon-common
         cinnamon-screensaver
-        nemo
-        xapps
+        nemo-with-extensions
+        xapp
       ];
       services.cinnamon.apps.enable = mkDefault true;
       services.gnome.glib-networking.enable = true;
@@ -117,11 +132,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
@@ -142,28 +154,36 @@ 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
+
+        # other
+        glib # for gsettings
+        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
-        vanilla-dmz
+      ] config.environment.cinnamon.excludePackages);
 
-        # other
-        glib # for gsettings
-        shared-mime-info # for update-mime-database
-        xdg-user-dirs
-      ];
+      xdg.mime.enable = true;
+      xdg.icons.enable = true;
 
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
@@ -177,7 +197,7 @@ in
       programs.bash.vteIntegration = mkDefault true;
       programs.zsh.vteIntegration = mkDefault true;
 
-      # Harmonize Qt5 applications under Pantheon
+      # Harmonize Qt5 applications under Cinnamon
       qt5.enable = true;
       qt5.platformTheme = "gnome";
       qt5.style = "adwaita";
@@ -199,19 +219,19 @@ in
       environment.systemPackages = with pkgs // pkgs.gnome // pkgs.cinnamon; utils.removePackagesByName [
         # cinnamon team apps
         bulky
-        blueberry
         warpinator
 
-        # cinnamon xapps
+        # cinnamon xapp
         xviewer
         xreader
-        xed
+        xed-editor
         xplayer
         pix
 
         # external apps shipped with linux-mint
         hexchat
         gnome-calculator
+        gnome-screenshot
       ] config.environment.cinnamon.excludePackages;
     })
   ];
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index 8247a7e381c9..510561246a2b 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -18,7 +18,7 @@ in
   # determines the default: later modules (if enabled) are preferred.
   # E.g., if Plasma 5 is enabled, it supersedes xterm.
   imports = [
-    ./none.nix ./xterm.nix ./xfce.nix ./plasma5.nix ./lumina.nix
+    ./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
@@ -33,25 +33,25 @@ in
           type = types.enum [ "center" "fill" "max" "scale" "tile" ];
           default = "scale";
           example = "fill";
-          description = ''
-            The file <filename>~/.background-image</filename> is used as a background image.
+          description = lib.mdDoc ''
+            The file {file}`~/.background-image` is used as a background image.
             This option specifies the placement of this image onto your desktop.
 
             Possible values:
-            <literal>center</literal>: Center the image on the background. If it is too small, it will be surrounded by a black border.
-            <literal>fill</literal>: Like <literal>scale</literal>, but preserves aspect ratio by zooming the image until it fits. Either a horizontal or a vertical part of the image will be cut off.
-            <literal>max</literal>: Like <literal>fill</literal>, but scale the image to the maximum size that fits the screen with black borders on one side.
-            <literal>scale</literal>: Fit the file into the background without repeating it, cutting off stuff or using borders. But the aspect ratio is not preserved either.
-            <literal>tile</literal>: Tile (repeat) the image in case it is too small for the screen.
+            `center`: Center the image on the background. If it is too small, it will be surrounded by a black border.
+            `fill`: Like `scale`, but preserves aspect ratio by zooming the image until it fits. Either a horizontal or a vertical part of the image will be cut off.
+            `max`: Like `fill`, but scale the image to the maximum size that fits the screen with black borders on one side.
+            `scale`: Fit the file into the background without repeating it, cutting off stuff or using borders. But the aspect ratio is not preserved either.
+            `tile`: Tile (repeat) the image in case it is too small for the screen.
           '';
         };
 
         combineScreens = mkOption {
           type = types.bool;
           default = false;
-          description = ''
-            When set to <literal>true</literal> the wallpaper will stretch across all screens.
-            When set to <literal>false</literal> the wallpaper is duplicated to all screens.
+          description = lib.mdDoc ''
+            When set to `true` the wallpaper will stretch across all screens.
+            When set to `false` the wallpaper is duplicated to all screens.
           '';
         };
       };
@@ -64,15 +64,17 @@ 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";
           start = d.start
+          # literal newline to ensure d.start's last line is not appended to
           + optionalString (needBGCond d) ''
+
             if [ -e $HOME/.background-image ]; then
               ${pkgs.feh}/bin/feh --bg-${cfg.wallpaper.mode} ${optionalString cfg.wallpaper.combineScreens "--no-xinerama"} $HOME/.background-image
             fi
@@ -84,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/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index d1513a596b9f..2de5d845d68b 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -16,6 +16,10 @@ let
 in
 
 {
+  meta = {
+    maintainers = teams.enlightenment.members;
+  };
+
   imports = [
     (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "e19" "enable" ] [ "services" "xserver" "desktopManager" "enlightenment" "enable" ])
   ];
@@ -25,7 +29,7 @@ in
     services.xserver.desktopManager.enlightenment.enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable the Enlightenment desktop environment.";
+      description = lib.mdDoc "Enable the Enlightenment desktop environment.";
     };
 
   };
@@ -92,6 +96,7 @@ in
 
     services.udisks2.enable = true;
     services.upower.enable = config.powerManagement.enable;
+    services.xserver.libinput.enable = mkDefault true;
 
     services.dbus.packages = [ e.efl ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index e7e626c66f02..9c1978e362bc 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/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>
@@ -165,31 +137,30 @@ 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 = {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable GNOME desktop manager.";
+        description = lib.mdDoc "Enable GNOME desktop manager.";
       };
 
       sessionPath = mkOption {
         default = [];
         type = types.listOf types.package;
         example = literalExpression "[ pkgs.gnome.gpaste ]";
-        description = ''
+        description = lib.mdDoc ''
           Additional list of packages to be added to the session search path.
           Useful for GNOME Shell extensions or GSettings-conditional autostart.
 
           Note that this should be a last resort; patching the package is preferred (see GPaste).
         '';
-        apply = list: list ++ [ pkgs.gnome.gnome-shell pkgs.gnome.gnome-shell-extensions ];
       };
 
       favoriteAppsOverride = mkOption {
@@ -202,44 +173,44 @@ 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 {
         default = "";
         type = types.lines;
-        description = "Additional gsettings overrides.";
+        description = lib.mdDoc "Additional gsettings overrides.";
       };
 
       extraGSettingsOverridePackages = mkOption {
         default = [];
         type = types.listOf types.path;
-        description = "List of packages for which gsettings are overridden.";
+        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 {
             options = {
               wmName = mkOption {
                 type = types.strMatching "[a-zA-Z0-9_-]+";
-                description = "A unique identifier for the window manager.";
+                description = lib.mdDoc "A unique identifier for the window manager.";
                 example = "xmonad";
               };
 
               wmLabel = mkOption {
                 type = types.str;
-                description = "The name of the window manager to show in the session chooser.";
+                description = lib.mdDoc "The name of the window manager to show in the session chooser.";
                 example = "XMonad";
               };
 
               wmCommand = mkOption {
                 type = types.str;
-                description = "The executable of the window manager to use.";
+                description = lib.mdDoc "The executable of the window manager to use.";
                 example = literalExpression ''"''${pkgs.haskellPackages.xmonad}/bin/xmonad"'';
               };
 
@@ -247,22 +218,22 @@ in
                 type = types.bool;
                 default = true;
                 example = false;
-                description = "Whether to enable the GNOME panel in this session.";
+                description = lib.mdDoc "Whether to enable the GNOME panel in this session.";
               };
             };
           });
           default = [];
-          description = "Other GNOME Flashback sessions to enable.";
+          description = lib.mdDoc "Other GNOME Flashback sessions to enable.";
         };
 
         panelModulePackages = mkOption {
           default = [ pkgs.gnome.gnome-applets ];
           defaultText = literalExpression "[ pkgs.gnome.gnome-applets ]";
           type = types.listOf types.path;
-          description = ''
-            Packages containing modules that should be made available to <literal>gnome-panel</literal> (usually for applets).
+          description = lib.mdDoc ''
+            Packages containing modules that should be made available to `gnome-panel` (usually for applets).
 
-            If you're packaging something to use here, please install the modules in <literal>$out/lib/gnome-panel/modules</literal>.
+            If you're packaging something to use here, please install the modules in `$out/lib/gnome-panel/modules`.
           '';
         };
       };
@@ -272,7 +243,7 @@ in
       default = [];
       example = literalExpression "[ pkgs.gnome.totem ]";
       type = types.listOf types.package;
-      description = "Which packages gnome should exclude from the default environment";
+      description = lib.mdDoc "Which packages gnome should exclude from the default environment";
     };
 
   };
@@ -362,11 +333,16 @@ in
       services.gnome.tracker-miners.enable = mkDefault true;
       services.gnome.tracker.enable = mkDefault true;
       services.hardware.bolt.enable = mkDefault true;
-      services.packagekit.enable = mkDefault true;
+      # TODO: Enable once #177946 is resolved
+      # services.packagekit.enable = mkDefault true;
       services.udisks2.enable = true;
       services.upower.enable = config.powerManagement.enable;
       services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center
 
+      # Explicitly enabled since GNOME will be severely broken without these.
+      xdg.mime.enable = true;
+      xdg.icons.enable = true;
+
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [
         pkgs.xdg-desktop-portal-gnome
@@ -400,9 +376,21 @@ in
     })
 
     (mkIf serviceCfg.core-shell.enable {
+      services.xserver.desktopManager.gnome.sessionPath =
+        let
+          mandatoryPackages = [
+            pkgs.gnome.gnome-shell
+          ];
+          optionalPackages = [
+            pkgs.gnome.gnome-shell-extensions
+          ];
+        in
+        mandatoryPackages
+        ++ 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;
@@ -452,26 +440,31 @@ in
       ];
 
       # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-38/elements/core/meta-gnome-core-shell.bst
-      environment.systemPackages = with pkgs.gnome; [
-        adwaita-icon-theme
-        nixos-background-info
-        gnome-backgrounds
-        gnome-bluetooth
-        gnome-color-manager
-        gnome-control-center
-        gnome-shell
-        gnome-shell-extensions
-        gnome-themes-extra
-        pkgs.gnome-tour # GNOME Shell detects the .desktop file on first log-in.
-        pkgs.gnome-user-docs
-        pkgs.orca
-        pkgs.glib # for gsettings
-        pkgs.gnome-menus
-        pkgs.gtk3.out # for gtk-launch
-        pkgs.hicolor-icon-theme
-        pkgs.shared-mime-info # for update-mime-database
-        pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
-      ];
+      environment.systemPackages =
+        let
+          mandatoryPackages = with pkgs.gnome; [
+            gnome-shell
+          ];
+          optionalPackages = with pkgs.gnome; [
+            adwaita-icon-theme
+            nixos-background-info
+            gnome-backgrounds
+            gnome-bluetooth
+            gnome-color-manager
+            gnome-control-center
+            gnome-shell-extensions
+            gnome-themes-extra
+            pkgs.gnome-tour # GNOME Shell detects the .desktop file on first log-in.
+            pkgs.gnome-user-docs
+            pkgs.orca
+            pkgs.glib # for gsettings program
+            pkgs.gnome-menus
+            pkgs.gtk3.out # for gtk-launch program
+            pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+          ];
+        in
+        mandatoryPackages
+        ++ utils.removePackagesByName optionalPackages config.environment.gnome.excludePackages;
     })
 
     # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-38/elements/core/meta-gnome-core-utilities.bst
@@ -527,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/nixos/modules/services/x11/desktop-managers/kodi.nix b/nixos/modules/services/x11/desktop-managers/kodi.nix
index b853c94d6fd4..43904cd00e84 100644
--- a/nixos/modules/services/x11/desktop-managers/kodi.nix
+++ b/nixos/modules/services/x11/desktop-managers/kodi.nix
@@ -12,7 +12,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable the kodi multimedia center.";
+        description = lib.mdDoc "Enable the kodi multimedia center.";
       };
 
       package = mkOption {
@@ -20,7 +20,7 @@ in
         default = pkgs.kodi;
         defaultText = literalExpression "pkgs.kodi";
         example = literalExpression "pkgs.kodi.withPackages (p: with p; [ jellyfin pvr-iptvsimple vfs-sftp ])";
-        description = ''
+        description = lib.mdDoc ''
           Package that should be used for Kodi.
         '';
       };
diff --git a/nixos/modules/services/x11/desktop-managers/lumina.nix b/nixos/modules/services/x11/desktop-managers/lumina.nix
index 419f5055d8be..7b694106bf7e 100644
--- a/nixos/modules/services/x11/desktop-managers/lumina.nix
+++ b/nixos/modules/services/x11/desktop-managers/lumina.nix
@@ -10,12 +10,16 @@ let
 in
 
 {
+  meta = {
+    maintainers = teams.lumina.members;
+  };
+
   options = {
 
     services.xserver.desktopManager.lumina.enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable the Lumina desktop manager";
+      description = lib.mdDoc "Enable the Lumina desktop manager";
     };
 
   };
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index 3c912d726118..b69da41c9fc9 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -9,19 +9,23 @@ let
 in
 
 {
+  meta = {
+    maintainers = teams.lxqt.members;
+  };
+
   options = {
 
     services.xserver.desktopManager.lxqt.enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable the LXQt desktop manager";
+      description = lib.mdDoc "Enable the LXQt desktop manager";
     };
 
     environment.lxqt.excludePackages = mkOption {
       default = [];
       example = literalExpression "[ pkgs.lxqt.qterminal ]";
       type = types.listOf types.package;
-      description = "Which LXQt packages to exclude from the default environment";
+      description = lib.mdDoc "Which LXQt packages to exclude from the default environment";
     };
 
   };
@@ -63,8 +67,9 @@ in
 
     services.upower.enable = config.powerManagement.enable;
 
-    xdg.portal.enable = true;
-    xdg.portal.extraPortals = [ pkgs.lxqt.xdg-desktop-portal-lxqt ];
+    services.xserver.libinput.enable = mkDefault true;
+
+    xdg.portal.lxqt.enable = true;
   };
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index 9ab4c6e7e984..c93f120bed7f 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -16,17 +16,17 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable the MATE desktop environment";
+        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 {
       default = [];
       example = literalExpression "[ pkgs.mate.mate-terminal pkgs.mate.pluma ]";
       type = types.listOf types.package;
-      description = "Which MATE packages to exclude from the default environment";
+      description = lib.mdDoc "Which MATE packages to exclude from the default environment";
     };
 
   };
@@ -73,6 +73,7 @@ in
     services.udev.packages = [ pkgs.mate.mate-settings-daemon ];
     services.gvfs.enable = true;
     services.upower.enable = config.powerManagement.enable;
+    services.xserver.libinput.enable = mkDefault true;
 
     security.pam.services.mate-screensaver.unixAuth = true;
 
diff --git a/nixos/modules/services/x11/desktop-managers/none.nix b/nixos/modules/services/x11/desktop-managers/none.nix
index b5e498b67a01..074b729cc3f3 100644
--- a/nixos/modules/services/x11/desktop-managers/none.nix
+++ b/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/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 004d14b634d4..5c0203224e13 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -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");
 
     };
 
@@ -37,50 +37,46 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable the pantheon desktop manager";
+        description = lib.mdDoc "Enable the pantheon desktop manager";
       };
 
       sessionPath = mkOption {
         default = [];
         type = types.listOf types.package;
         example = literalExpression "[ pkgs.gnome.gpaste ]";
-        description = ''
+        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).
         '';
-        apply = list: list ++
-        [
-          pkgs.pantheon.pantheon-agent-geoclue2
-        ];
       };
 
       extraWingpanelIndicators = mkOption {
         default = null;
         type = with types; nullOr (listOf package);
-        description = "Indicators to add to Wingpanel.";
+        description = lib.mdDoc "Indicators to add to Wingpanel.";
       };
 
       extraSwitchboardPlugs = mkOption {
         default = null;
         type = with types; nullOr (listOf package);
-        description = "Plugs to add to Switchboard.";
+        description = lib.mdDoc "Plugs to add to Switchboard.";
       };
 
       extraGSettingsOverrides = mkOption {
         default = "";
         type = types.lines;
-        description = "Additional gsettings overrides.";
+        description = lib.mdDoc "Additional gsettings overrides.";
       };
 
       extraGSettingsOverridePackages = mkOption {
         default = [];
         type = types.listOf types.path;
-        description = "List of packages for which gsettings are overridden.";
+        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");
 
     };
 
@@ -88,7 +84,7 @@ in
       default = [];
       example = literalExpression "[ pkgs.pantheon.elementary-camera ]";
       type = types.listOf types.package;
-      description = "Which packages pantheon should exclude from the default environment";
+      description = lib.mdDoc "Which packages pantheon should exclude from the default environment";
     };
 
   };
@@ -96,6 +92,9 @@ in
 
   config = mkMerge [
     (mkIf cfg.enable {
+      services.xserver.desktopManager.pantheon.sessionPath = utils.removePackagesByName [
+        pkgs.pantheon.pantheon-agent-geoclue2
+      ] config.environment.pantheon.excludePackages;
 
       services.xserver.displayManager.sessionPackages = [ pkgs.pantheon.elementary-session-settings ];
 
@@ -135,7 +134,9 @@ in
       services.bamf.enable = true;
       services.colord.enable = mkDefault true;
       services.fwupd.enable = mkDefault true;
-      services.packagekit.enable = mkDefault true;
+      # TODO: Enable once #177946 is resolved
+      # services.packagekit.enable = mkDefault true;
+      services.power-profiles-daemon.enable = mkDefault true;
       services.touchegg.enable = mkDefault true;
       services.touchegg.package = pkgs.pantheon.touchegg;
       services.tumbler.enable = mkDefault true;
@@ -167,28 +168,37 @@ in
         isSystem = true;
       };
       services.udev.packages = [
-        pkgs.gnome.gnome-settings-daemon338
+        pkgs.pantheon.gnome-settings-daemon
       ];
       systemd.packages = [
-        pkgs.gnome.gnome-settings-daemon338
+        pkgs.pantheon.gnome-settings-daemon
       ];
       programs.dconf.enable = true;
       networking.networkmanager.enable = mkDefault true;
 
       # Global environment
-      environment.systemPackages = with pkgs; [
+      environment.systemPackages = (with pkgs.pantheon; [
+        elementary-session-settings
+        elementary-settings-daemon
+        gala
+        gnome-settings-daemon
+        (switchboard-with-plugs.override {
+          plugs = cfg.extraSwitchboardPlugs;
+        })
+        (wingpanel-with-indicators.override {
+          indicators = cfg.extraWingpanelIndicators;
+        })
+      ]) ++ utils.removePackagesByName ((with pkgs; [
         desktop-file-utils
-        glib
+        glib # for gsettings program
         gnome-menus
         gnome.adwaita-icon-theme
-        gtk3.out
-        hicolor-icon-theme
+        gtk3.out # for gtk-launch program
         onboard
         qgnomeplatform
-        shared-mime-info
         sound-theme-freedesktop
-        xdg-user-dirs
-      ] ++ (with pkgs.pantheon; [
+        xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+      ]) ++ (with pkgs.pantheon; [
         # Artwork
         elementary-gtk-theme
         elementary-icon-theme
@@ -198,33 +208,21 @@ in
         # Desktop
         elementary-default-settings
         elementary-dock
-        elementary-session-settings
         elementary-shortcut-overlay
-        gala
-        (switchboard-with-plugs.override {
-          plugs = cfg.extraSwitchboardPlugs;
-        })
-        (wingpanel-with-indicators.override {
-          indicators = cfg.extraWingpanelIndicators;
-        })
 
         # Services
         elementary-capnet-assist
         elementary-notifications
-        elementary-settings-daemon
         pantheon-agent-geoclue2
         pantheon-agent-polkit
-      ]) ++ (utils.removePackagesByName [
-        gnome.gnome-font-viewer
-        gnome.gnome-settings-daemon338
-      ] config.environment.pantheon.excludePackages);
-
-      programs.evince.enable = mkDefault true;
-      programs.file-roller.enable = mkDefault true;
+      ])) config.environment.pantheon.excludePackages;
 
       # Settings from elementary-default-settings
       environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini";
 
+      xdg.mime.enable = true;
+      xdg.icons.enable = true;
+
       xdg.portal.enable = true;
       xdg.portal.extraPortals = with pkgs.pantheon; [
         elementary-files
@@ -272,7 +270,12 @@ in
     })
 
     (mkIf serviceCfg.apps.enable {
-      environment.systemPackages = with pkgs.pantheon; utils.removePackagesByName ([
+      programs.evince.enable = mkDefault true;
+      programs.file-roller.enable = mkDefault true;
+
+      environment.systemPackages = utils.removePackagesByName ([
+        pkgs.gnome.gnome-font-viewer
+      ] ++ (with pkgs.pantheon; [
         elementary-calculator
         elementary-calendar
         elementary-camera
@@ -282,7 +285,7 @@ in
         elementary-music
         elementary-photos
         elementary-screenshot
-        elementary-tasks
+        # elementary-tasks
         elementary-terminal
         elementary-videos
         epiphany
@@ -290,7 +293,8 @@ in
         # Only install appcenter if flatpak is enabled before
         # https://github.com/NixOS/nixpkgs/issues/15932 is resolved.
         appcenter
-      ]) config.environment.pantheon.excludePackages;
+        sideload
+      ])) config.environment.pantheon.excludePackages;
 
       # needed by screenshot
       fonts.fonts = [
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixos/modules/services/x11/desktop-managers/pantheon.xml
index 202909d398f0..6226f8f6a272 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.xml
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.xml
@@ -3,7 +3,7 @@
          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 3 and Granite.
+  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>
@@ -89,9 +89,9 @@ switchboard-with-plugs.override {
      </para>
     </listitem>
    </varlistentry>
-   <varlistentry xml:id="sec-pantheon-faq-gnome3-and-pantheon">
+   <varlistentry xml:id="sec-pantheon-faq-gnome-and-pantheon">
     <term>
-     I cannot enable both GNOME 3 and Pantheon.
+     I cannot enable both GNOME and Pantheon.
     </term>
     <listitem>
      <para>
diff --git a/nixos/modules/programs/phosh.nix b/nixos/modules/services/x11/desktop-managers/phosh.nix
index ad875616ac9e..e889c0e34e7d 100644
--- a/nixos/modules/programs/phosh.nix
+++ b/nixos/modules/services/x11/desktop-managers/phosh.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  cfg = config.programs.phosh;
+  cfg = config.services.xserver.desktopManager.phosh;
 
   # Based on https://source.puri.sm/Librem5/librem5-base/-/blob/4596c1056dd75ac7f043aede07887990fd46f572/default/sm.puri.OSK0.desktop
   oskItem = pkgs.makeDesktopItem {
@@ -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,15 +75,21 @@ let
         example = "768x1024";
       };
       scale = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Display scaling factor.
         '';
-        type = types.nullOr types.ints.unsigned;
+        type = types.nullOr (
+          types.addCheck
+          (types.either types.int types.float)
+          (x : x > 0)
+        ) // {
+          description = "null or positive integer or float";
+        };
         default = null;
         example = 2;
       };
       rotate = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Screen transformation.
         '';
         type = types.enum [
@@ -118,14 +124,41 @@ let
     [cursor]
     theme = ${phoc.cursorTheme}
   '';
-in {
+in
+
+{
   options = {
-    programs.phosh = {
-      enable = mkEnableOption ''
-        Whether to enable, Phosh, related packages and default configurations.
-      '';
+    services.xserver.desktopManager.phosh = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable the Phone Shell.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.phosh;
+        defaultText = literalExpression "pkgs.phosh";
+        example = literalExpression "pkgs.phosh";
+        description = lib.mdDoc ''
+          Package that should be used for Phosh.
+        '';
+      };
+
+      user = mkOption {
+        description = lib.mdDoc "The user to run the Phosh service.";
+        type = types.str;
+        example = "alice";
+      };
+
+      group = mkOption {
+        description = lib.mdDoc "The group to run the Phosh service.";
+        type = types.str;
+        example = "users";
+      };
+
       phocConfig = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Configurations for the Phoc compositor.
         '';
         type = types.oneOf [ types.lines types.path phocConfigType ];
@@ -135,14 +168,42 @@ in {
   };
 
   config = mkIf cfg.enable {
+    systemd.defaultUnit = "graphical.target";
+    # Inspired by https://gitlab.gnome.org/World/Phosh/phosh/-/blob/main/data/phosh.service
+    systemd.services.phosh = {
+      wantedBy = [ "graphical.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/phosh";
+        User = cfg.user;
+        Group = cfg.group;
+        PAMName = "login";
+        WorkingDirectory = "~";
+        Restart = "always";
+
+        TTYPath = "/dev/tty7";
+        TTYReset = "yes";
+        TTYVHangup = "yes";
+        TTYVTDisallocate = "yes";
+
+        # Fail to start if not controlling the tty.
+        StandardInput = "tty-fail";
+        StandardOutput = "journal";
+        StandardError = "journal";
+
+        # Log this user with utmp, letting it show up with commands 'w' and 'who'.
+        UtmpIdentifier = "tty7";
+        UtmpMode = "user";
+      };
+    };
+
     environment.systemPackages = [
       pkgs.phoc
-      pkgs.phosh
+      cfg.package
       pkgs.squeekboard
       oskItem
     ];
 
-    systemd.packages = [ pkgs.phosh ];
+    systemd.packages = [ cfg.package ];
 
     programs.feedbackd.enable = true;
 
@@ -152,7 +213,7 @@ in {
 
     services.gnome.core-shell.enable = true;
     services.gnome.core-os-services.enable = true;
-    services.xserver.displayManager.sessionPackages = [ pkgs.phosh ];
+    services.xserver.displayManager.sessionPackages = [ cfg.package ];
 
     environment.etc."phosh/phoc.ini".source =
       if builtins.isPath cfg.phocConfig then cfg.phocConfig
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 3ca044ad5bc8..9fcb408c287d 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 let
   xcfg = config.services.xserver;
@@ -30,7 +30,7 @@ let
   inherit (libsForQt5) kdeGear kdeFrameworks plasma5;
   inherit (pkgs) writeText;
   inherit (lib)
-    getBin optionalString
+    getBin optionalString literalExpression
     mkRemovedOptionModule mkRenamedOptionModule
     mkDefault mkIf mkMerge mkOption types;
 
@@ -157,23 +157,22 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable the Plasma 5 (KDE 5) desktop environment.";
+      description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment.";
     };
 
     phononBackend = mkOption {
       type = types.enum [ "gstreamer" "vlc" ];
       default = "gstreamer";
       example = "vlc";
-      description = "Phonon audio backend to install.";
+      description = lib.mdDoc "Phonon audio backend to install.";
     };
 
     supportDDC = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Support setting monitor brightness via DDC.
-        </para>
-        <para>
+
         This is not needed for controlling brightness of the internal monitor
         of a laptop and as it is considered experimental by upstream, it is
         disabled by default.
@@ -183,13 +182,20 @@ in
     useQtScaling = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable HiDPI scaling in Qt.";
+      description = lib.mdDoc "Enable HiDPI scaling in Qt.";
     };
 
     runUsingSystemd = mkOption {
-      description = "Use systemd to manage the Plasma session";
+      description = lib.mdDoc "Use systemd to manage the Plasma session";
       type = types.bool;
-      default = false;
+      default = true;
+    };
+
+    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
@@ -209,7 +215,7 @@ in
     mobile.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable support for running the Plasma Mobile shell.
       '';
     };
@@ -217,11 +223,19 @@ in
     mobile.installRecommendedSoftware = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      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.
+      '';
+    };
   };
 
   imports = [
@@ -231,14 +245,14 @@ in
 
   config = mkMerge [
     # Common Plasma dependencies
-    (mkIf (cfg.enable || cfg.mobile.enable) {
+    (mkIf (cfg.enable || cfg.mobile.enable || cfg.bigscreen.enable) {
 
       security.wrappers = {
-        kcheckpass = {
+        kscreenlocker_greet = {
           setuid = true;
           owner = "root";
           group = "root";
-          source = "${getBin libsForQt5.kscreenlocker}/libexec/kcheckpass";
+          source = "${getBin libsForQt5.kscreenlocker}/libexec/kscreenlocker_greet";
         };
         start_kdeinit = {
           setuid = true;
@@ -263,89 +277,97 @@ in
       environment.systemPackages =
         with libsForQt5;
         with plasma5; with kdeGear; with kdeFrameworks;
-        [
-          frameworkintegration
-          kactivities
-          kauth
-          kcmutils
-          kconfig
-          kconfigwidgets
-          kcoreaddons
-          kdoctools
-          kdbusaddons
-          kdeclarative
-          kded
-          kdesu
-          kdnssd
-          kemoticons
-          kfilemetadata
-          kglobalaccel
-          kguiaddons
-          kiconthemes
-          kidletime
-          kimageformats
-          kinit
-          kirigami2 # In system profile for SDDM theme. TODO: wrapper.
-          kio
-          kjobwidgets
-          knewstuff
-          knotifications
-          knotifyconfig
-          kpackage
-          kparts
-          kpeople
-          krunner
-          kservice
-          ktextwidgets
-          kwallet
-          kwallet-pam
-          kwalletmanager
-          kwayland
-          kwayland-integration
-          kwidgetsaddons
-          kxmlgui
-          kxmlrpcclient
-          plasma-framework
-          solid
-          sonnet
-          threadweaver
-
-          breeze-qt5
-          kactivitymanagerd
-          kde-cli-tools
-          kdecoration
-          kdeplasma-addons
-          kgamma5
-          khotkeys
-          kscreen
-          kscreenlocker
-          kwayland
-          kwin
-          kwrited
-          libkscreen
-          libksysguard
-          milou
-          plasma-browser-integration
-          plasma-integration
-          polkit-kde-agent
-
-          plasma-desktop
-          plasma-workspace
-          plasma-workspace-wallpapers
-
-          konsole
-          oxygen
-
-          breeze-icons
-          pkgs.hicolor-icon-theme
-
-          kde-gtk-config
-          breeze-gtk
-
-          qtvirtualkeyboard
-
-          pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
-        ]
+        let
+          requiredPackages = [
+            frameworkintegration
+            kactivities
+            kauth
+            kcmutils
+            kconfig
+            kconfigwidgets
+            kcoreaddons
+            kdoctools
+            kdbusaddons
+            kdeclarative
+            kded
+            kdesu
+            kdnssd
+            kemoticons
+            kfilemetadata
+            kglobalaccel
+            kguiaddons
+            kiconthemes
+            kidletime
+            kimageformats
+            kinit
+            kirigami2 # In system profile for SDDM theme. TODO: wrapper.
+            kio
+            kjobwidgets
+            knewstuff
+            knotifications
+            knotifyconfig
+            kpackage
+            kparts
+            kpeople
+            krunner
+            kservice
+            ktextwidgets
+            kwallet
+            kwallet-pam
+            kwalletmanager
+            kwayland
+            kwayland-integration
+            kwidgetsaddons
+            kxmlgui
+            kxmlrpcclient
+            plasma-framework
+            solid
+            sonnet
+            threadweaver
+
+            breeze-qt5
+            kactivitymanagerd
+            kde-cli-tools
+            kdecoration
+            kdeplasma-addons
+            kgamma5
+            khotkeys
+            kscreen
+            kscreenlocker
+            kwayland
+            kwin
+            kwrited
+            libkscreen
+            libksysguard
+            milou
+            plasma-integration
+            polkit-kde-agent
+
+            plasma-desktop
+            plasma-workspace
+            plasma-workspace-wallpapers
+
+            oxygen-sounds
+
+            breeze-icons
+            pkgs.hicolor-icon-theme
+
+            kde-gtk-config
+            breeze-gtk
+
+            qtvirtualkeyboard
+
+            pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+          ];
+          optionalPackages = [
+            plasma-browser-integration
+            konsole
+            oxygen
+            (lib.getBin qttools) # Expose qdbus in PATH
+          ];
+        in
+        requiredPackages
+        ++ utils.removePackagesByName optionalPackages cfg.excludePackages
 
         # Phonon audio backend
         ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
@@ -362,6 +384,11 @@ in
         ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]
         ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet;
 
+      # 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
         "/share"
@@ -387,9 +414,10 @@ in
       services.accounts-daemon.enable = true;
       # when changing an account picture the accounts-daemon reads a temporary file containing the image which systemsettings5 may place under /tmp
       systemd.services.accounts-daemon.serviceConfig.PrivateTmp = false;
+      services.power-profiles-daemon.enable = mkDefault true;
+      services.system-config-printer.enable = mkIf config.services.printing.enable (mkDefault true);
       services.udisks2.enable = true;
       services.upower.enable = config.powerManagement.enable;
-      services.system-config-printer.enable = mkIf config.services.printing.enable (mkDefault true);
       services.xserver.libinput.enable = mkDefault true;
 
       # Extra UDEV rules used by Solid
@@ -423,17 +451,23 @@ 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
@@ -457,27 +491,29 @@ in
       environment.systemPackages =
         with libsForQt5;
         with plasma5; with kdeGear; with kdeFrameworks;
-        [
-          ksystemstats
-          kinfocenter
-          kmenuedit
-          plasma-systemmonitor
-          spectacle
-          systemsettings
-
-          dolphin
-          dolphin-plugins
-          ffmpegthumbs
-          kdegraphics-thumbnailers
-          khelpcenter
-          kio-extras
-          print-manager
-
-          elisa
-          gwenview
-          okular
-        ]
-      ;
+        let
+          requiredPackages = [
+            ksystemstats
+            kinfocenter
+            kmenuedit
+            plasma-systemmonitor
+            spectacle
+            systemsettings
+
+            dolphin
+            dolphin-plugins
+            ffmpegthumbs
+            kdegraphics-thumbnailers
+            kio-extras
+          ];
+          optionalPackages = [
+            elisa
+            gwenview
+            okular
+            khelpcenter
+            print-manager
+          ];
+      in requiredPackages ++ utils.removePackagesByName optionalPackages cfg.excludePackages;
 
       systemd.user.services = {
         plasma-run-with-systemd = {
@@ -549,6 +585,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
@@ -562,9 +600,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)
@@ -575,5 +613,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/nixos/modules/services/x11/desktop-managers/retroarch.nix b/nixos/modules/services/x11/desktop-managers/retroarch.nix
index d471673d4521..5552f37612a2 100644
--- a/nixos/modules/services/x11/desktop-managers/retroarch.nix
+++ b/nixos/modules/services/x11/desktop-managers/retroarch.nix
@@ -6,21 +6,21 @@ 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;
       default = pkgs.retroarch;
       defaultText = literalExpression "pkgs.retroarch";
       example = literalExpression "pkgs.retroarch-full";
-      description = "RetroArch package to use.";
+      description = lib.mdDoc "RetroArch package to use.";
     };
 
     extraArgs = mkOption {
       type = types.listOf types.str;
       default = [ ];
       example = [ "--verbose" "--host" ];
-      description = "Extra arguments to pass to RetroArch.";
+      description = lib.mdDoc "Extra arguments to pass to RetroArch.";
     };
   };
 
diff --git a/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixos/modules/services/x11/desktop-managers/surf-display.nix
index 4b5a04f988ba..38ebb9d02b4a 100644
--- a/nixos/modules/services/x11/desktop-managers/surf-display.nix
+++ b/nixos/modules/services/x11/desktop-managers/surf-display.nix
@@ -45,21 +45,21 @@ 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;
         default = "${pkgs.surf-display}/share/surf-display/empty-page.html";
         defaultText = literalExpression ''"''${pkgs.surf-display}/share/surf-display/empty-page.html"'';
         example = "https://www.example.com/";
-        description = "Default URI to display.";
+        description = lib.mdDoc "Default URI to display.";
       };
 
       inactivityInterval = mkOption {
         type = types.int;
         default = 300;
         example = 0;
-        description = ''
+        description = lib.mdDoc ''
           Setting for internal inactivity timer to restart surf-display if the
           user goes inactive/idle to get a fresh session for the next user of
           the kiosk.
@@ -72,18 +72,18 @@ in {
       screensaverSettings = mkOption {
         type = types.separatedString " ";
         default = "";
-        description = ''
-          Screensaver settings, see <literal>man 1 xset</literal> for possible options.
+        description = lib.mdDoc ''
+          Screensaver settings, see `man 1 xset` for possible options.
         '';
       };
 
       pointerButtonMap = mkOption {
         type = types.str;
         default = "1 0 0 4 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
-        description = ''
+        description = lib.mdDoc ''
           Disable right and middle pointer device click in browser sessions
           while keeping scrolling wheels' functionality intact. See pointer
-          subcommand on <literal>man xmodmap</literal> for details.
+          subcommand on `man xmodmap` for details.
         '';
       };
 
@@ -91,7 +91,7 @@ in {
         type = types.str;
         default = "yes";
         example = "no";
-        description = "Hide idle mouse pointer.";
+        description = lib.mdDoc "Hide idle mouse pointer.";
       };
 
       extraConfig = mkOption {
@@ -111,8 +111,8 @@ in {
           DISPLAYS['display-host-3']="www_uri=https://www.displayserver.comany.net/display-4/index.html"|res=1280x1024"
           DISPLAYS['display-host-local-file']="www_uri=file:///usr/share/doc/surf-display/empty-page.html"
         '';
-        description = ''
-          Extra configuration options to append to <literal>/etc/default/surf-display</literal>.
+        description = lib.mdDoc ''
+          Extra configuration options to append to `/etc/default/surf-display`.
         '';
       };
     };
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 88b21e59aaa6..eee1f63ebdcc 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -4,10 +4,9 @@ with lib;
 
 let
   cfg = config.services.xserver.desktopManager.xfce;
-in
 
+in
 {
-
   meta = {
     maintainers = teams.xfce.members;
   };
@@ -36,6 +35,12 @@ in
       [ "services" "xserver" "desktopManager" "xfce" "extraSessionCommands" ]
       [ "services" "xserver" "displayManager" "sessionCommands" ])
     (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "xfce" "screenLock" ] "")
+
+    # added 2022-06-26
+    # thunar has its own module
+    (mkRenamedOptionModule
+      [ "services" "xserver" "desktopManager" "xfce" "thunarPlugins" ]
+      [ "programs" "thunar" "plugins" ])
   ];
 
   options = {
@@ -43,34 +48,25 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable the Xfce desktop environment.";
-      };
-
-      thunarPlugins = mkOption {
-        default = [];
-        type = types.listOf types.package;
-        example = literalExpression "[ pkgs.xfce.thunar-archive-plugin ]";
-        description = ''
-          A list of plugin that should be installed with Thunar.
-        '';
+        description = lib.mdDoc "Enable the Xfce desktop environment.";
       };
 
       noDesktop = mkOption {
         type = types.bool;
         default = false;
-        description = "Don't install XFCE desktop components (xfdesktop and panel).";
+        description = lib.mdDoc "Don't install XFCE desktop components (xfdesktop and panel).";
       };
 
       enableXfwm = mkOption {
         type = types.bool;
         default = true;
-        description = "Enable the XFWM (default) window manager.";
+        description = lib.mdDoc "Enable the XFWM (default) window manager.";
       };
 
       enableScreensaver = mkOption {
         type = types.bool;
         default = true;
-        description = "Enable the XFCE screensaver.";
+        description = lib.mdDoc "Enable the XFCE screensaver.";
       };
     };
   };
@@ -98,7 +94,6 @@ in
       exo
       garcon
       libxfce4ui
-      xfconf
 
       mousepad
       parole
@@ -110,8 +105,6 @@ in
       xfce4-settings
       xfce4-taskmanager
       xfce4-terminal
-
-      (thunar.override { thunarPlugins = cfg.thunarPlugins; })
     ] # TODO: NetworkManager doesn't belong here
       ++ optional config.networking.networkmanager.enable networkmanagerapplet
       ++ optional config.powerManagement.enable xfce4-power-manager
@@ -130,6 +123,9 @@ in
         xfdesktop
       ] ++ optional cfg.enableScreensaver xfce4-screensaver;
 
+    programs.xfconf.enable = true;
+    programs.thunar.enable = true;
+
     environment.pathsToLink = [
       "/share/xfce4"
       "/lib/xfce4"
@@ -170,7 +166,6 @@ in
 
     # Systemd services
     systemd.packages = with pkgs.xfce; [
-      (thunar.override { thunarPlugins = cfg.thunarPlugins; })
       xfce4-notifyd
     ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixos/modules/services/x11/desktop-managers/xterm.nix
index 3424ee1b0e11..2b439effabe5 100644
--- a/nixos/modules/services/x11/desktop-managers/xterm.nix
+++ b/nixos/modules/services/x11/desktop-managers/xterm.nix
@@ -16,7 +16,7 @@ in
       type = types.bool;
       default = versionOlder config.system.stateVersion "19.09" && xSessionEnabled;
       defaultText = literalExpression ''versionOlder config.system.stateVersion "19.09" && config.services.xserver.enable;'';
-      description = "Enable a xterm terminal as a desktop manager.";
+      description = lib.mdDoc "Enable a xterm terminal as a desktop manager.";
     };
 
   };
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index a5db3dd5dd45..995ecd231c43 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/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
@@ -35,6 +35,10 @@ let
       # Shared environment setup for graphical sessions.
 
       . /etc/profile
+      if test -f ~/.profile; then
+          source ~/.profile
+      fi
+
       cd "$HOME"
 
       # Allow the user to execute commands at the beginning of the X session.
@@ -149,25 +153,25 @@ 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 {
         type = types.path;
-        description = "Path to the X server used by display managers.";
+        description = lib.mdDoc "Path to the X server used by display managers.";
       };
 
       xserverArgs = mkOption {
         type = types.listOf types.str;
         default = [];
         example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
-        description = "List of arguments for the X server.";
+        description = lib.mdDoc "List of arguments for the X server.";
       };
 
       setupCommands = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed just after the X server has started.
 
           This option is only effective for display managers for which this feature
@@ -182,7 +186,7 @@ in
           ''
             xmessage "Hello World!" &
           '';
-        description = ''
+        description = lib.mdDoc ''
           Shell commands executed just before the window or desktop manager is
           started. These commands are not currently sourced for Wayland sessions.
         '';
@@ -191,7 +195,7 @@ in
       hiddenUsers = mkOption {
         type = types.listOf types.str;
         default = [ "nobody" ];
-        description = ''
+        description = lib.mdDoc ''
           A list of users which will not be shown in the display manager.
         '';
       };
@@ -212,7 +216,7 @@ in
            '';
         });
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           A list of packages containing x11 or wayland session files to be passed to the display manager.
         '';
       };
@@ -231,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
@@ -248,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: {
@@ -281,11 +285,11 @@ in
             defaultSessionFromLegacyOptions
           else
             null;
-        defaultText = literalDocBook ''
+        defaultText = literalMD ''
           Taken from display manager settings or window manager settings, if either is set.
         '';
         example = "gnome";
-        description = ''
+        description = lib.mdDoc ''
           Graphical session to pre-select in the session chooser (only effective for GDM, LightDM and SDDM).
 
           On GDM, LightDM and SDDM, it will also be used as a session for auto-login.
@@ -295,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.
         '';
       };
@@ -306,34 +310,34 @@ in
           type = types.lines;
           default = "";
           example = "rm -f /var/log/my-display-manager.log";
-          description = "Script executed before the display manager is started.";
+          description = lib.mdDoc "Script executed before the display manager is started.";
         };
 
         execCmd = mkOption {
           type = types.str;
           example = literalExpression ''"''${pkgs.lightdm}/bin/lightdm"'';
-          description = "Command to start the display manager.";
+          description = lib.mdDoc "Command to start the display manager.";
         };
 
         environment = mkOption {
           type = types.attrsOf types.unspecified;
           default = {};
-          description = "Additional environment variables needed by the display manager.";
+          description = lib.mdDoc "Additional environment variables needed by the display manager.";
         };
 
         logToFile = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             Whether the display manager redirects the output of the
-            session script to <filename>~/.xsession-errors</filename>.
+            session script to {file}`~/.xsession-errors`.
           '';
         };
 
         logToJournal = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Whether the display manager redirects the output of the
             session script to the systemd journal.
           '';
@@ -349,15 +353,15 @@ in
               type = types.bool;
               default = config.user != null;
               defaultText = literalExpression "config.${options.user} != null";
-              description = ''
-                Automatically log in as <option>autoLogin.user</option>.
+              description = lib.mdDoc ''
+                Automatically log in as {option}`autoLogin.user`.
               '';
             };
 
             user = mkOption {
               type = types.nullOr types.str;
               default = null;
-              description = ''
+              description = lib.mdDoc ''
                 User to be used for the automatic login.
               '';
             };
@@ -365,7 +369,7 @@ in
         });
 
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           Auto login configuration attrset.
         '';
       };
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 70ae6b8978d0..1c3881bef2de 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -67,15 +67,15 @@ 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 {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Seconds of inactivity after which the autologin will be performed.
         '';
       };
@@ -83,14 +83,14 @@ in
       wayland = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Allow GDM to run on Wayland instead of Xserver.
         '';
       };
 
       autoSuspend = mkOption {
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           On the GNOME Display Manager login screen, suspend the machine after inactivity.
           (Does not affect automatic suspend while logged in, or at lock screen.)
         '';
@@ -103,9 +103,9 @@ in
         example = {
           debug.enable = true;
         };
-        description = ''
+        description = lib.mdDoc ''
           Options passed to the gdm daemon.
-          See <link xlink:href="https://help.gnome.org/admin/gdm/stable/configuration.html.en#daemonconfig">here</link> for supported options.
+          See [here](https://help.gnome.org/admin/gdm/stable/configuration.html.en#daemonconfig) for supported options.
         '';
       };
 
@@ -140,8 +140,13 @@ in
         environment = {
           GDM_X_SERVER_EXTRA_ARGS = toString
             (filter (arg: arg != "-terminate") cfg.xserverArgs);
-          # GDM is needed for gnome-login.session
-          XDG_DATA_DIRS = "${gdm}/share:${cfg.sessionData.desktops}/share:${pkgs.gnome.gnome-control-center}/share";
+          XDG_DATA_DIRS = lib.makeSearchPath "share" [
+            gdm # for gnome-login.session
+            cfg.sessionData.desktops
+            pkgs.gnome.gnome-control-center # for accessibility icon
+            pkgs.gnome.adwaita-icon-theme
+            pkgs.hicolor-icon-theme # empty icon theme as a base
+          ];
         } // optionalAttrs (xSessionWrapper != null) {
           # Make GDM use this wrapper before running the session, which runs the
           # configured setupCommands. This relies on a patched GDM which supports
@@ -298,7 +303,7 @@ in
 
         session  required       pam_succeed_if.so audit quiet_success user = gdm
         session  required       pam_env.so conffile=/etc/pam/environment readenv=0
-        session  optional       ${pkgs.systemd}/lib/security/pam_systemd.so
+        session  optional       ${config.systemd.package}/lib/security/pam_systemd.so
         session  optional       pam_keyinit.so force revoke
         session  optional       pam_permit.so
       '';
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
index 930ee96b384d..412bcc4091b3 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
@@ -26,7 +26,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable enso-os-greeter as the lightdm greeter
         '';
       };
@@ -36,7 +36,7 @@ in {
           type = types.package;
           default = pkgs.gnome.gnome-themes-extra;
           defaultText = literalExpression "pkgs.gnome.gnome-themes-extra";
-          description = ''
+          description = lib.mdDoc ''
             The package path that contains the theme given in the name option.
           '';
         };
@@ -44,7 +44,7 @@ in {
         name = mkOption {
           type = types.str;
           default = "Adwaita";
-          description = ''
+          description = lib.mdDoc ''
             Name of the theme to use for the lightdm-enso-os-greeter
           '';
         };
@@ -55,7 +55,7 @@ in {
           type = types.package;
           default = pkgs.papirus-icon-theme;
           defaultText = literalExpression "pkgs.papirus-icon-theme";
-          description = ''
+          description = lib.mdDoc ''
             The package path that contains the icon theme given in the name option.
           '';
         };
@@ -63,7 +63,7 @@ in {
         name = mkOption {
           type = types.str;
           default = "ePapirus";
-          description = ''
+          description = lib.mdDoc ''
             Name of the icon theme to use for the lightdm-enso-os-greeter
           '';
         };
@@ -74,7 +74,7 @@ in {
           type = types.package;
           default = pkgs.capitaine-cursors;
           defaultText = literalExpression "pkgs.capitaine-cursors";
-          description = ''
+          description = lib.mdDoc ''
             The package path that contains the cursor theme given in the name option.
           '';
         };
@@ -82,7 +82,7 @@ in {
         name = mkOption {
           type = types.str;
           default = "capitane-cursors";
-          description = ''
+          description = lib.mdDoc ''
             Name of the cursor theme to use for the lightdm-enso-os-greeter
           '';
         };
@@ -91,7 +91,7 @@ in {
       blur = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether or not to enable blur
         '';
       };
@@ -99,7 +99,7 @@ in {
       brightness = mkOption {
         type = types.int;
         default = 7;
-        description = ''
+        description = lib.mdDoc ''
           Brightness
         '';
       };
@@ -107,7 +107,7 @@ in {
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration that should be put in the greeter.conf
           configuration file
         '';
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
index debd4b568bf6..c050367e74df 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -38,7 +38,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable lightdm-gtk-greeter as the lightdm greeter.
         '';
       };
@@ -49,7 +49,7 @@ in
           type = types.package;
           default = pkgs.gnome.gnome-themes-extra;
           defaultText = literalExpression "pkgs.gnome.gnome-themes-extra";
-          description = ''
+          description = lib.mdDoc ''
             The package path that contains the theme given in the name option.
           '';
         };
@@ -57,7 +57,7 @@ in
         name = mkOption {
           type = types.str;
           default = "Adwaita";
-          description = ''
+          description = lib.mdDoc ''
             Name of the theme to use for the lightdm-gtk-greeter.
           '';
         };
@@ -70,7 +70,7 @@ in
           type = types.package;
           default = pkgs.gnome.adwaita-icon-theme;
           defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme";
-          description = ''
+          description = lib.mdDoc ''
             The package path that contains the icon theme given in the name option.
           '';
         };
@@ -78,7 +78,7 @@ in
         name = mkOption {
           type = types.str;
           default = "Adwaita";
-          description = ''
+          description = lib.mdDoc ''
             Name of the icon theme to use for the lightdm-gtk-greeter.
           '';
         };
@@ -91,7 +91,7 @@ in
           type = types.package;
           default = pkgs.gnome.adwaita-icon-theme;
           defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme";
-          description = ''
+          description = lib.mdDoc ''
             The package path that contains the cursor theme given in the name option.
           '';
         };
@@ -99,7 +99,7 @@ in
         name = mkOption {
           type = types.str;
           default = "Adwaita";
-          description = ''
+          description = lib.mdDoc ''
             Name of the cursor theme to use for the lightdm-gtk-greeter.
           '';
         };
@@ -107,7 +107,7 @@ in
         size = mkOption {
           type = types.int;
           default = 16;
-          description = ''
+          description = lib.mdDoc ''
             Size of the cursor theme to use for the lightdm-gtk-greeter.
           '';
         };
@@ -117,7 +117,7 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "%F";
-        description = ''
+        description = lib.mdDoc ''
           Clock format string (as expected by strftime, e.g. "%H:%M")
           to use with the lightdm gtk greeter panel.
 
@@ -129,7 +129,7 @@ in
         type = types.nullOr (types.listOf types.str);
         default = null;
         example = [ "~host" "~spacer" "~clock" "~spacer" "~session" "~language" "~a11y" "~power" ];
-        description = ''
+        description = lib.mdDoc ''
           List of allowed indicator modules to use for the lightdm gtk
           greeter panel.
 
@@ -145,7 +145,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration that should be put in the lightdm-gtk-greeter.conf
           configuration file.
         '';
@@ -158,7 +158,7 @@ in
   config = mkIf (ldmcfg.enable && cfg.enable) {
 
     services.xserver.displayManager.lightdm.greeter = mkDefault {
-      package = pkgs.lightdm_gtk_greeter.xgreeters;
+      package = pkgs.lightdm-gtk-greeter.xgreeters;
       name = "lightdm-gtk-greeter";
     };
 
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
index 16d7fdf15cf6..f4195c4c2dc3 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
@@ -55,19 +55,19 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable lightdm-mini-greeter as the lightdm greeter.
 
           Note that this greeter starts only the default X session.
           You can configure the default X session using
-          <xref linkend="opt-services.xserver.displayManager.defaultSession"/>.
+          [](#opt-services.xserver.displayManager.defaultSession).
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "root";
-        description = ''
+        description = lib.mdDoc ''
           The user to login as.
         '';
       };
@@ -75,7 +75,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra configuration that should be put in the lightdm-mini-greeter.conf
           configuration file.
         '';
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/mobile.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/mobile.nix
new file mode 100644
index 000000000000..31cc9b3deaa1
--- /dev/null
+++ b/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/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
index f18e4a914e57..10707e001e82 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
@@ -21,7 +21,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable elementary-greeter as the lightdm greeter.
         '';
       };
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
new file mode 100644
index 000000000000..4456374cc569
--- /dev/null
+++ b/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/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
index a9ba8e6280d6..8d6bfa98a7e4 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
@@ -17,12 +17,12 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable lightdm-tiny-greeter as the lightdm greeter.
 
           Note that this greeter starts only the default X session.
           You can configure the default X session using
-          <xref linkend="opt-services.xserver.displayManager.defaultSession"/>.
+          [](#opt-services.xserver.displayManager.defaultSession).
         '';
       };
 
@@ -30,7 +30,7 @@ in
         user = mkOption {
           type = types.str;
           default = "Username";
-          description = ''
+          description = lib.mdDoc ''
             The string to represent the user_text label.
           '';
         };
@@ -38,7 +38,7 @@ in
         pass = mkOption {
           type = types.str;
           default = "Password";
-          description = ''
+          description = lib.mdDoc ''
             The string to represent the pass_text label.
           '';
         };
@@ -48,7 +48,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Section to describe style and ui.
         '';
       };
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 27dfed3cc14c..f74e8efb8f64 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -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"
@@ -105,7 +107,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable lightdm as the display manager.
         '';
       };
@@ -114,14 +116,14 @@ in
         enable = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             If set to false, run lightdm in greeterless mode. This only works if autologin
             is enabled and autoLogin.timeout is zero.
           '';
         };
         package = mkOption {
           type = types.package;
-          description = ''
+          description = lib.mdDoc ''
             The LightDM greeter to login via. The package should be a directory
             containing a .desktop file matching the name in the 'name' option.
           '';
@@ -129,7 +131,7 @@ in
         };
         name = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             The name of a .desktop file in the directory specified
             in the 'package' option.
           '';
@@ -142,14 +144,14 @@ in
         example = ''
           user-authority-in-system-dir = true
         '';
-        description = "Extra lines to append to LightDM section.";
+        description = lib.mdDoc "Extra lines to append to LightDM section.";
       };
 
       background = mkOption {
         type = types.either types.path (types.strMatching "^#[0-9]\{6\}$");
         # Manual cannot depend on packages, we are actually setting the default in config below.
         defaultText = literalExpression "pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath";
-        description = ''
+        description = lib.mdDoc ''
           The background image or color to use.
         '';
       };
@@ -160,14 +162,14 @@ in
         example = ''
           greeter-show-manual-login=true
         '';
-        description = "Extra lines to append to SeatDefaults section.";
+        description = lib.mdDoc "Extra lines to append to SeatDefaults section.";
       };
 
       # Configuration for automatic login specific to LightDM
       autoLogin.timeout = mkOption {
         type = types.int;
         default = 0;
-        description = ''
+        description = lib.mdDoc ''
           Show the greeter for this many seconds before automatic login occurs.
         '';
       };
@@ -287,7 +289,7 @@ in
 
         session  required       pam_succeed_if.so audit quiet_success user = lightdm
         session  required       pam_env.so conffile=/etc/pam/environment readenv=0
-        session  optional       ${pkgs.systemd}/lib/security/pam_systemd.so
+        session  optional       ${config.systemd.package}/lib/security/pam_systemd.so
         session  optional       pam_keyinit.so force revoke
         session  optional       pam_permit.so
     '';
@@ -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/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 529a086381f0..a3f03d7a19a6 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -100,7 +100,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable sddm as the display manager.
         '';
       };
@@ -108,7 +108,7 @@ in
       enableHidpi = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable automatic HiDPI mode.
         '';
       };
@@ -122,15 +122,15 @@ in
             Session = "plasma.desktop";
           };
         };
-        description = ''
-          Extra settings merged in and overwritting defaults in sddm.conf.
+        description = lib.mdDoc ''
+          Extra settings merged in and overwriting defaults in sddm.conf.
         '';
       };
 
       theme = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Greeter theme to use.
         '';
       };
@@ -138,7 +138,7 @@ in
       autoNumlock = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable numlock at login.
         '';
       };
@@ -151,16 +151,16 @@ in
           xrandr --setprovideroutputsource modesetting NVIDIA-0
           xrandr --auto
         '';
-        description = ''
+        description = lib.mdDoc ''
           A script to execute when starting the display server. DEPRECATED, please
-          use <option>services.xserver.displayManager.setupCommands</option>.
+          use {option}`services.xserver.displayManager.setupCommands`.
         '';
       };
 
       stopScript = mkOption {
         type = types.str;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           A script to execute when stopping the display server.
         '';
       };
@@ -170,7 +170,7 @@ in
         relogin = mkOption {
           type = types.bool;
           default = false;
-          description = ''
+          description = lib.mdDoc ''
             If true automatic login will kick in again on session exit (logout), otherwise it
             will only log in automatically when the display-manager is started.
           '';
@@ -179,7 +179,7 @@ in
         minimumUid = mkOption {
           type = types.ints.u16;
           default = 1000;
-          description = ''
+          description = lib.mdDoc ''
             Minimum user ID for auto-login user.
           '';
         };
@@ -231,7 +231,7 @@ in
 
         session  required       pam_succeed_if.so audit quiet_success user = sddm
         session  required       pam_env.so conffile=/etc/pam/environment readenv=0
-        session  optional       ${pkgs.systemd}/lib/security/pam_systemd.so
+        session  optional       ${config.systemd.package}/lib/security/pam_systemd.so
         session  optional       pam_keyinit.so force revoke
         session  optional       pam_permit.so
       '';
@@ -269,20 +269,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/nixos/modules/services/x11/display-managers/startx.nix b/nixos/modules/services/x11/display-managers/startx.nix
index a48566ae0684..f4bb7a89d03b 100644
--- a/nixos/modules/services/x11/display-managers/startx.nix
+++ b/nixos/modules/services/x11/display-managers/startx.nix
@@ -17,7 +17,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the dummy "startx" pseudo-display manager,
           which allows users to start X manually via the "startx" command
           from a vt shell. The X server runs under the user's id, not as root.
diff --git a/nixos/modules/services/x11/display-managers/sx.nix b/nixos/modules/services/x11/display-managers/sx.nix
index e30977364300..6a7fc1a040e7 100644
--- a/nixos/modules/services/x11/display-managers/sx.nix
+++ b/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/nixos/modules/services/x11/display-managers/xpra.nix b/nixos/modules/services/x11/display-managers/xpra.nix
index c23e479140f0..cb78f52d9b68 100644
--- a/nixos/modules/services/x11/display-managers/xpra.nix
+++ b/nixos/modules/services/x11/display-managers/xpra.nix
@@ -16,27 +16,34 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable xpra as display manager.";
+        description = lib.mdDoc "Whether to enable xpra as display manager.";
       };
 
       bindTcp = mkOption {
         default = "127.0.0.1:10000";
         example = "0.0.0.0:10000";
         type = types.nullOr types.str;
-        description = "Bind xpra to TCP";
+        description = lib.mdDoc "Bind xpra to TCP";
+      };
+
+      desktop = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "gnome-shell";
+        description = lib.mdDoc "Start a desktop environment instead of seamless mode";
       };
 
       auth = mkOption {
         type = types.str;
         default = "pam";
         example = "password:value=mysecret";
-        description = "Authentication to use when connecting to xpra";
+        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 = "Extra xpra options";
+        description = lib.mdDoc "Extra xpra options";
         default = [];
         type = types.listOf types.str;
       };
@@ -222,7 +229,7 @@ in
     services.xserver.displayManager.job.execCmd = ''
       ${optionalString (cfg.pulseaudio)
         "export PULSE_COOKIE=/run/pulse/.config/pulse/cookie"}
-      exec ${pkgs.xpra}/bin/xpra start \
+      exec ${pkgs.xpra}/bin/xpra ${if cfg.desktop == null then "start" else "start-desktop --start=${cfg.desktop}"} \
         --daemon=off \
         --log-dir=/var/log \
         --log-file=xpra.log \
diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
index 159bed63e137..574657a50c82 100644
--- a/nixos/modules/services/x11/extra-layouts.nix
+++ b/nixos/modules/services/x11/extra-layouts.nix
@@ -9,13 +9,13 @@ let
     options = {
       description = mkOption {
         type = types.str;
-        description = "A short description of the layout.";
+        description = lib.mdDoc "A short description of the layout.";
       };
 
       languages = mkOption {
         type = types.listOf types.str;
         description =
-        ''
+        lib.mdDoc ''
           A list of languages provided by the layout.
           (Use ISO 639-2 codes, for example: "eng" for english)
         '';
@@ -24,55 +24,55 @@ let
       compatFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to the xkb compat file.
           This file sets the compatibility state, used to preserve
           compatibility with xkb-unaware programs.
-          It must contain a <literal>xkb_compat "name" { ... }</literal> block.
+          It must contain a `xkb_compat "name" { ... }` block.
         '';
       };
 
       geometryFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to the xkb geometry file.
           This (completely optional) file describes the physical layout of
           keyboard, which maybe be used by programs to depict it.
-          It must contain a <literal>xkb_geometry "name" { ... }</literal> block.
+          It must contain a `xkb_geometry "name" { ... }` block.
         '';
       };
 
       keycodesFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to the xkb keycodes file.
           This file specifies the range and the interpretation of the raw
           keycodes sent by the keyboard.
-          It must contain a <literal>xkb_keycodes "name" { ... }</literal> block.
+          It must contain a `xkb_keycodes "name" { ... }` block.
         '';
       };
 
       symbolsFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to the xkb symbols file.
           This is the most important file: it defines which symbol or action
           maps to each key and must contain a
-          <literal>xkb_symbols "name" { ... }</literal> block.
+          `xkb_symbols "name" { ... }` block.
         '';
       };
 
       typesFile = mkOption {
         type = types.nullOr types.path;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           The path to the xkb types file.
           This file specifies the key types that can be associated with
           the various keyboard keys.
-          It must contain a <literal>xkb_types "name" { ... }</literal> block.
+          It must contain a `xkb_types "name" { ... }` block.
         '';
       };
 
@@ -103,12 +103,12 @@ in
           };
         }
       '';
-      description = ''
+      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:
-        <link xlink:href="https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts"></link>.
+        [](https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts).
         For more examples see
-        <link xlink:href="https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples"></link>
+        [](https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples)
       '';
     };
 
diff --git a/nixos/modules/services/x11/fractalart.nix b/nixos/modules/services/x11/fractalart.nix
index 448248a58794..f7fc1ec96228 100644
--- a/nixos/modules/services/x11/fractalart.nix
+++ b/nixos/modules/services/x11/fractalart.nix
@@ -8,21 +8,21 @@ in {
       type = types.bool;
       default = false;
       example = true;
-      description = "Enable FractalArt for generating colorful wallpapers on login";
+      description = lib.mdDoc "Enable FractalArt for generating colorful wallpapers on login";
     };
 
     width = mkOption {
       type = types.nullOr types.int;
       default = null;
       example = 1920;
-      description = "Screen width";
+      description = lib.mdDoc "Screen width";
     };
 
     height = mkOption {
       type = types.nullOr types.int;
       default = null;
       example = 1080;
-      description = "Screen height";
+      description = lib.mdDoc "Screen height";
     };
   };
 
diff --git a/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix
index 3fd6fed91e13..2105224f92ff 100644
--- a/nixos/modules/services/x11/gdk-pixbuf.nix
+++ b/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -1,43 +1,26 @@
 { 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 = "Packages providing GDK-Pixbuf modules, for cache generation.";
+      description = lib.mdDoc "Packages providing GDK-Pixbuf modules, for cache generation.";
     };
   };
 
   # If there is any package configured in modulePackages, we generate the
   # loaders.cache based on that and set the environment variable
   # GDK_PIXBUF_MODULE_FILE to point to it.
-  config = mkIf (cfg.modulePackages != []) {
+  config = lib.mkIf (cfg.modulePackages != []) {
     environment.variables = {
       GDK_PIXBUF_MODULE_FILE = "${loadersCache}";
     };
diff --git a/nixos/modules/services/x11/hardware/cmt.nix b/nixos/modules/services/x11/hardware/cmt.nix
index 5ac824c5e419..a44221141c3c 100644
--- a/nixos/modules/services/x11/hardware/cmt.nix
+++ b/nixos/modules/services/x11/hardware/cmt.nix
@@ -15,14 +15,14 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Enable chrome multitouch input (cmt). Touchpad drivers that are configured for chromebooks.";
+        description = lib.mdDoc "Enable chrome multitouch input (cmt). Touchpad drivers that are configured for chromebooks.";
       };
       models = mkOption {
         type = types.enum [ "atlas" "banjo" "candy" "caroline" "cave" "celes" "clapper" "cyan" "daisy" "elan" "elm" "enguarde" "eve" "expresso" "falco" "gandof" "glimmer" "gnawty" "heli" "kevin" "kip" "leon" "lulu" "orco" "pbody" "peppy" "pi" "pit" "puppy" "quawks" "rambi" "samus" "snappy" "spring" "squawks" "swanky" "winky" "wolf" "auron_paine" "auron_yuna" "daisy_skate" "nyan_big" "nyan_blaze" "veyron_jaq" "veyron_jerry" "veyron_mighty" "veyron_minnie" "veyron_speedy" ];
         example = "banjo";
-        description = ''
+        description = lib.mdDoc ''
           Which models to enable cmt for. Enter the Code Name for your Chromebook.
-          Code Name can be found at <link xlink:href="https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices" />.
+          Code Name can be found at <https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices>.
         '';
       };
     }; #closes services
diff --git a/nixos/modules/services/x11/hardware/digimend.nix b/nixos/modules/services/x11/hardware/digimend.nix
index b1b1682f00b2..f82aac41a320 100644
--- a/nixos/modules/services/x11/hardware/digimend.nix
+++ b/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/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index efdb7c61dfae..f77036360e02 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -12,8 +12,8 @@ let cfg = config.services.xserver.libinput;
         default = null;
         example = "/dev/input/event0";
         description =
-          ''
-            Path for ${deviceType} device.  Set to <literal>null</literal> to apply to any
+          lib.mdDoc ''
+            Path for ${deviceType} device.  Set to `null` to apply to any
             auto-detected ${deviceType}.
           '';
       };
@@ -23,14 +23,14 @@ let cfg = config.services.xserver.libinput;
         default = "adaptive";
         example = "flat";
         description =
-          ''
+          lib.mdDoc ''
             Sets the pointer acceleration profile to the given profile.
-            Permitted values are <literal>adaptive</literal>, <literal>flat</literal>.
+            Permitted values are `adaptive`, `flat`.
             Not all devices support this option or all profiles.
             If a profile is unsupported, the default profile for this is used.
-            <literal>flat</literal>: Pointer motion is accelerated by a constant
+            `flat`: Pointer motion is accelerated by a constant
             (device-specific) factor, depending on the current speed.
-            <literal>adaptive</literal>: Pointer acceleration depends on the input speed.
+            `adaptive`: Pointer acceleration depends on the input speed.
             This is the default profile for most devices.
           '';
       };
@@ -39,7 +39,7 @@ let cfg = config.services.xserver.libinput;
         type = types.nullOr types.str;
         default = null;
         example = "-0.5";
-        description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
+        description = lib.mdDoc "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
       };
 
       buttonMapping = mkOption {
@@ -47,7 +47,7 @@ let cfg = config.services.xserver.libinput;
         default = null;
         example = "1 6 3 4 5 0 7";
         description =
-          ''
+          lib.mdDoc ''
             Sets the logical button mapping for this device, see XSetPointerMapping(3). The string  must
             be  a  space-separated  list  of  button mappings in the order of the logical buttons on the
             device, starting with button 1.  The default mapping is "1 2 3 ... 32". A mapping of 0 deac‐
@@ -62,7 +62,7 @@ let cfg = config.services.xserver.libinput;
         default = null;
         example = "0.5 0 0 0 0.8 0.1 0 0 1";
         description =
-          ''
+          lib.mdDoc ''
             A string of 9 space-separated floating point numbers. Sets the calibration matrix to the
             3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
           '';
@@ -73,9 +73,9 @@ let cfg = config.services.xserver.libinput;
         default = null;
         example = "buttonareas";
         description =
-          ''
-            Enables a click method. Permitted values are <literal>none</literal>,
-            <literal>buttonareas</literal>, <literal>clickfinger</literal>.
+          lib.mdDoc ''
+            Enables a click method. Permitted values are `none`,
+            `buttonareas`, `clickfinger`.
             Not all devices support all methods, if an option is unsupported,
             the default click method for this device is used.
           '';
@@ -84,14 +84,14 @@ let cfg = config.services.xserver.libinput;
       leftHanded = mkOption {
         type = types.bool;
         default = false;
-        description = "Enables left-handed button orientation, i.e. swapping left and right buttons.";
+        description = lib.mdDoc "Enables left-handed button orientation, i.e. swapping left and right buttons.";
       };
 
       middleEmulation = mkOption {
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             Enables middle button emulation. When enabled, pressing the left and right buttons
             simultaneously produces a middle mouse button click.
           '';
@@ -100,7 +100,7 @@ let cfg = config.services.xserver.libinput;
       naturalScrolling = mkOption {
         type = types.bool;
         default = false;
-        description = "Enables or disables natural scrolling behavior.";
+        description = lib.mdDoc "Enables or disables natural scrolling behavior.";
       };
 
       scrollButton = mkOption {
@@ -108,7 +108,7 @@ let cfg = config.services.xserver.libinput;
         default = null;
         example = 1;
         description =
-          ''
+          lib.mdDoc ''
             Designates a button as scroll button. If the ScrollMethod is button and the button is logically
             held down, x/y axis movement is converted into scroll events.
           '';
@@ -119,9 +119,9 @@ let cfg = config.services.xserver.libinput;
         default = "twofinger";
         example = "edge";
         description =
-          ''
-            Specify the scrolling method: <literal>twofinger</literal>, <literal>edge</literal>,
-            <literal>button</literal>, or <literal>none</literal>
+          lib.mdDoc ''
+            Specify the scrolling method: `twofinger`, `edge`,
+            `button`, or `none`
           '';
       };
 
@@ -129,7 +129,7 @@ let cfg = config.services.xserver.libinput;
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             Disables horizontal scrolling. When disabled, this driver will discard any horizontal scroll
             events from libinput. Note that this does not disable horizontal scrolling, it merely
             discards the horizontal axis from any scroll events.
@@ -141,9 +141,9 @@ let cfg = config.services.xserver.libinput;
         default = "enabled";
         example = "disabled";
         description =
-          ''
-            Sets the send events mode to <literal>disabled</literal>, <literal>enabled</literal>,
-            or <literal>disabled-on-external-mouse</literal>
+          lib.mdDoc ''
+            Sets the send events mode to `disabled`, `enabled`,
+            or `disabled-on-external-mouse`
           '';
       };
 
@@ -151,19 +151,27 @@ let cfg = config.services.xserver.libinput;
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             Enables or disables tap-to-click behavior.
           '';
       };
 
+      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;
         description =
-          ''
+          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.
           '';
       };
 
@@ -171,7 +179,7 @@ let cfg = config.services.xserver.libinput;
         type = types.nullOr types.str;
         default = null;
         example = "0.5 0 0 0 0.8 0.1 0 0 1";
-        description = ''
+        description = lib.mdDoc ''
           A string of 9 space-separated floating point numbers. Sets the transformation matrix to
           the 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
         '';
@@ -181,7 +189,7 @@ let cfg = config.services.xserver.libinput;
         type = types.bool;
         default = false;
         description =
-          ''
+          lib.mdDoc ''
             Disable input method while typing.
           '';
       };
@@ -193,9 +201,9 @@ let cfg = config.services.xserver.libinput;
         ''
           Option "DragLockButtons" "L1 B1 L2 B2"
         '';
-        description = ''
+        description = lib.mdDoc ''
           Additional options for libinput ${deviceType} driver. See
-          <citerefentry><refentrytitle>libinput</refentrytitle><manvolnum>4</manvolnum></citerefentry>
+          {manpage}`libinput(4)`
           for available options.";
         '';
       };
@@ -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,7 @@ in {
   options = {
 
     services.xserver.libinput = {
-      enable = mkEnableOption "libinput";
+      enable = mkEnableOption (lib.mdDoc "libinput");
       mouse = mkConfigForDevice "mouse";
       touchpad = mkConfigForDevice "touchpad";
     };
diff --git a/nixos/modules/services/x11/hardware/synaptics.nix b/nixos/modules/services/x11/hardware/synaptics.nix
index 93dd560bca40..7b45222ac64c 100644
--- a/nixos/modules/services/x11/hardware/synaptics.nix
+++ b/nixos/modules/services/x11/hardware/synaptics.nix
@@ -30,7 +30,7 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable touchpad support. Deprecated: Consider services.xserver.libinput.enable.";
+        description = lib.mdDoc "Whether to enable touchpad support. Deprecated: Consider services.xserver.libinput.enable.";
       };
 
       dev = mkOption {
@@ -38,7 +38,7 @@ in {
         default = null;
         example = "/dev/input/event0";
         description =
-          ''
+          lib.mdDoc ''
             Path for touchpad device.  Set to null to apply to any
             auto-detected touchpad.
           '';
@@ -47,73 +47,73 @@ in {
       accelFactor = mkOption {
         type = types.nullOr types.str;
         default = "0.001";
-        description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
+        description = lib.mdDoc "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
       };
 
       minSpeed = mkOption {
         type = types.nullOr types.str;
         default = "0.6";
-        description = "Cursor speed factor for precision finger motion.";
+        description = lib.mdDoc "Cursor speed factor for precision finger motion.";
       };
 
       maxSpeed = mkOption {
         type = types.nullOr types.str;
         default = "1.0";
-        description = "Cursor speed factor for highest-speed finger motion.";
+        description = lib.mdDoc "Cursor speed factor for highest-speed finger motion.";
       };
 
       scrollDelta = mkOption {
         type = types.nullOr types.int;
         default = null;
         example = 75;
-        description = "Move distance of the finger for a scroll event.";
+        description = lib.mdDoc "Move distance of the finger for a scroll event.";
       };
 
       twoFingerScroll = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable two-finger drag-scrolling. Overridden by horizTwoFingerScroll and vertTwoFingerScroll.";
+        description = lib.mdDoc "Whether to enable two-finger drag-scrolling. Overridden by horizTwoFingerScroll and vertTwoFingerScroll.";
       };
 
       horizTwoFingerScroll = mkOption {
         type = types.bool;
         default = cfg.twoFingerScroll;
         defaultText = literalExpression "config.${opt.twoFingerScroll}";
-        description = "Whether to enable horizontal two-finger drag-scrolling.";
+        description = lib.mdDoc "Whether to enable horizontal two-finger drag-scrolling.";
       };
 
       vertTwoFingerScroll = mkOption {
         type = types.bool;
         default = cfg.twoFingerScroll;
         defaultText = literalExpression "config.${opt.twoFingerScroll}";
-        description = "Whether to enable vertical two-finger drag-scrolling.";
+        description = lib.mdDoc "Whether to enable vertical two-finger drag-scrolling.";
       };
 
       horizEdgeScroll = mkOption {
         type = types.bool;
         default = ! cfg.horizTwoFingerScroll;
         defaultText = literalExpression "! config.${opt.horizTwoFingerScroll}";
-        description = "Whether to enable horizontal edge drag-scrolling.";
+        description = lib.mdDoc "Whether to enable horizontal edge drag-scrolling.";
       };
 
       vertEdgeScroll = mkOption {
         type = types.bool;
         default = ! cfg.vertTwoFingerScroll;
         defaultText = literalExpression "! config.${opt.vertTwoFingerScroll}";
-        description = "Whether to enable vertical edge drag-scrolling.";
+        description = lib.mdDoc "Whether to enable vertical edge drag-scrolling.";
       };
 
       tapButtons = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable tap buttons.";
+        description = lib.mdDoc "Whether to enable tap buttons.";
       };
 
       buttonsMap = mkOption {
         type = types.listOf types.int;
         default = [1 2 3];
         example = [1 3 2];
-        description = "Remap touchpad buttons.";
+        description = lib.mdDoc "Remap touchpad buttons.";
         apply = map toString;
       };
 
@@ -121,34 +121,34 @@ in {
         type = types.listOf types.int;
         default = [1 2 3];
         example = [1 3 2];
-        description = "Remap several-fingers taps.";
+        description = lib.mdDoc "Remap several-fingers taps.";
         apply = map toString;
       };
 
       palmDetect = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable palm detection (hardware support required)";
+        description = lib.mdDoc "Whether to enable palm detection (hardware support required)";
       };
 
       palmMinWidth = mkOption {
         type = types.nullOr types.int;
         default = null;
         example = 5;
-        description = "Minimum finger width at which touch is considered a palm";
+        description = lib.mdDoc "Minimum finger width at which touch is considered a palm";
       };
 
       palmMinZ = mkOption {
         type = types.nullOr types.int;
         default = null;
         example = 20;
-        description = "Minimum finger pressure at which touch is considered a palm";
+        description = lib.mdDoc "Minimum finger pressure at which touch is considered a palm";
       };
 
       horizontalScroll = mkOption {
         type = types.bool;
         default = true;
-        description = "Whether to enable horizontal scrolling (on touchpad)";
+        description = lib.mdDoc "Whether to enable horizontal scrolling (on touchpad)";
       };
 
       additionalOptions = mkOption {
@@ -158,7 +158,7 @@ in {
           Option "RTCornerButton" "2"
           Option "RBCornerButton" "3"
         '';
-        description = ''
+        description = lib.mdDoc ''
           Additional options for synaptics touchpad driver.
         '';
       };
diff --git a/nixos/modules/services/x11/hardware/wacom.nix b/nixos/modules/services/x11/hardware/wacom.nix
index dad2b308d1b4..4994e5c1a2cc 100644
--- a/nixos/modules/services/x11/hardware/wacom.nix
+++ b/nixos/modules/services/x11/hardware/wacom.nix
@@ -17,13 +17,13 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the Wacom touchscreen/digitizer/tablet.
           If you ever have any issues such as, try switching to terminal (ctrl-alt-F1) and back
           which will make Xorg reconfigure the device ?
 
           If you're not satisfied by the default behaviour you can override
-          <option>environment.etc."X11/xorg.conf.d/70-wacom.conf"</option> in
+          {option}`environment.etc."X11/xorg.conf.d/70-wacom.conf"` in
           configuration.nix easily.
         '';
       };
diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix
index ae990141a502..133e64c65cdd 100644
--- a/nixos/modules/services/x11/imwheel.nix
+++ b/nixos/modules/services/x11/imwheel.nix
@@ -6,15 +6,15 @@ in
   {
     options = {
       services.xserver.imwheel = {
-        enable = mkEnableOption "IMWheel service";
+        enable = mkEnableOption (lib.mdDoc "IMWheel service");
 
         extraOptions = mkOption {
           type = types.listOf types.str;
           default = [ "--buttons=45" ];
           example = [ "--debug" ];
-          description = ''
+          description = lib.mdDoc ''
             Additional command-line arguments to pass to
-            <command>imwheel</command>.
+            {command}`imwheel`.
           '';
         };
 
@@ -33,12 +33,12 @@ in
               ''';
             }
           '';
-          description = ''
+          description = lib.mdDoc ''
             Window class translation rules.
             /etc/X11/imwheelrc is generated based on this config
             which means this config is global for all users.
-            See <link xlink:href="http://imwheel.sourceforge.net/imwheel.1.html">offical man pages</link>
-            for more informations.
+            See [official man pages](http://imwheel.sourceforge.net/imwheel.1.html)
+            for more information.
           '';
         };
       };
diff --git a/nixos/modules/services/x11/picom.nix b/nixos/modules/services/x11/picom.nix
index b40e20bcd357..4a0578de09cb 100644
--- a/nixos/modules/services/x11/picom.nix
+++ b/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
@@ -51,29 +42,29 @@ in {
 
   imports = [
     (mkAliasOptionModule [ "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 = {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether or not to enable Picom as the X.org composite manager.
       '';
     };
 
-    experimentalBackends = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether to use the unstable new reimplementation of the backends.
-      '';
-    };
-
     fade = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Fade windows in and out.
       '';
     };
@@ -82,16 +73,16 @@ in {
       type = types.ints.positive;
       default = 10;
       example = 5;
-      description = ''
+      description = lib.mdDoc ''
         Time between fade animation step (in ms).
       '';
     };
 
     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 = ''
+      description = lib.mdDoc ''
         Opacity change between fade steps (in and out).
       '';
     };
@@ -104,16 +95,16 @@ in {
         "name ~= 'Firefox$'"
         "focused = 1"
       ];
-      description = ''
+      description = lib.mdDoc ''
         List of conditions of windows that should not be faded.
-        See <literal>picom(1)</literal> man page for more examples.
+        See `picom(1)` man page for more examples.
       '';
     };
 
     shadow = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Draw window shadows.
       '';
     };
@@ -122,16 +113,16 @@ in {
       type = pairOf types.int;
       default = [ (-15) (-15) ];
       example = [ (-10) (-15) ];
-      description = ''
+      description = lib.mdDoc ''
         Left and right offset for shadows (in pixels).
       '';
     };
 
     shadowOpacity = mkOption {
-      type = floatBetween 0 1;
+      type = types.numbers.between 0 1;
       default = 0.75;
       example = 0.8;
-      description = ''
+      description = lib.mdDoc ''
         Window shadows opacity.
       '';
     };
@@ -144,35 +135,35 @@ in {
         "name ~= 'Firefox$'"
         "focused = 1"
       ];
-      description = ''
+      description = lib.mdDoc ''
         List of conditions of windows that should have no shadow.
-        See <literal>picom(1)</literal> man page for more examples.
+        See `picom(1)` man page for more examples.
       '';
     };
 
     activeOpacity = mkOption {
-      type = floatBetween 0 1;
+      type = types.numbers.between 0 1;
       default = 1.0;
       example = 0.8;
-      description = ''
+      description = lib.mdDoc ''
         Opacity of active windows.
       '';
     };
 
     inactiveOpacity = mkOption {
-      type = floatBetween 0.1 1;
+      type = types.numbers.between 0.1 1;
       default = 1.0;
       example = 0.8;
-      description = ''
+      description = lib.mdDoc ''
         Opacity of inactive windows.
       '';
     };
 
     menuOpacity = mkOption {
-      type = floatBetween 0 1;
+      type = types.numbers.between 0 1;
       default = 1.0;
       example = 0.8;
-      description = ''
+      description = lib.mdDoc ''
         Opacity of dropdown and popup menu.
       '';
     };
@@ -190,7 +181,7 @@ in {
         }
       '';
       example = {};
-      description = ''
+      description = lib.mdDoc ''
         Rules for specific window types.
       '';
     };
@@ -202,16 +193,16 @@ in {
         "95:class_g = 'URxvt' && !_NET_WM_STATE@:32a"
         "0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
       ];
-      description = ''
+      description = lib.mdDoc ''
         Rules that control the opacity of windows, in format PERCENT:PATTERN.
       '';
     };
 
     backend = mkOption {
-      type = types.enum [ "glx" "xrender" "xr_glx_hybrid" ];
+      type = types.enum [ "egl" "glx" "xrender" "xr_glx_hybrid" ];
       default = "xrender";
-      description = ''
-        Backend to use: <literal>glx</literal>, <literal>xrender</literal> or <literal>xr_glx_hybrid</literal>.
+      description = lib.mdDoc ''
+        Backend to use: `egl`, `glx`, `xrender` or `xr_glx_hybrid`.
       '';
     };
 
@@ -228,22 +219,13 @@ in {
           if isBool x then x
           else warn msg res;
 
-      description = ''
+      description = lib.mdDoc ''
         Enable vertical synchronization. Chooses the best method
         (drm, opengl, opengl-oml, opengl-swc, opengl-mswc) automatically.
         The bool value should be used, the others are just for backwards compatibility.
       '';
     };
 
-    refreshRate = mkOption {
-      type = types.ints.unsigned;
-      default = 0;
-      example = 60;
-      description = ''
-       Screen refresh rate (0 = automatically detect).
-      '';
-    };
-
     settings = with types;
     let
       scalar = oneOf [ bool int float str ]
@@ -271,10 +253,10 @@ in {
             deviation = 5.0;
           };
       '';
-      description = ''
+      description = lib.mdDoc ''
         Picom settings. Use this option to configure Picom settings not exposed
         in a NixOS option or to bypass one.  For the available options see the
-        CONFIGURATION FILES section at <literal>picom(1)</literal>.
+        CONFIGURATION FILES section at `picom(1)`.
       '';
     };
   };
@@ -306,7 +288,6 @@ in {
       # other options
       backend          = cfg.backend;
       vsync            = cfg.vSync;
-      refresh-rate     = cfg.refreshRate;
     };
 
     systemd.user.services.picom = {
@@ -320,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/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix
index cc9f964754f3..3eb9e28edae9 100644
--- a/nixos/modules/services/x11/redshift.nix
+++ b/nixos/modules/services/x11/redshift.nix
@@ -29,7 +29,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable Redshift to change your screen's colour temperature depending on
         the time of day.
       '';
@@ -39,17 +39,17 @@ in {
       day = mkOption {
         type = types.int;
         default = 5500;
-        description = ''
+        description = lib.mdDoc ''
           Colour temperature to use during the day, between
-          <literal>1000</literal> and <literal>25000</literal> K.
+          `1000` and `25000` K.
         '';
       };
       night = mkOption {
         type = types.int;
         default = 3700;
-        description = ''
+        description = lib.mdDoc ''
           Colour temperature to use at night, between
-          <literal>1000</literal> and <literal>25000</literal> K.
+          `1000` and `25000` K.
         '';
       };
     };
@@ -58,17 +58,17 @@ in {
       day = mkOption {
         type = types.str;
         default = "1";
-        description = ''
+        description = lib.mdDoc ''
           Screen brightness to apply during the day,
-          between <literal>0.1</literal> and <literal>1.0</literal>.
+          between `0.1` and `1.0`.
         '';
       };
       night = mkOption {
         type = types.str;
         default = "1";
-        description = ''
+        description = lib.mdDoc ''
           Screen brightness to apply during the night,
-          between <literal>0.1</literal> and <literal>1.0</literal>.
+          between `0.1` and `1.0`.
         '';
       };
     };
@@ -77,7 +77,7 @@ in {
       type = types.package;
       default = pkgs.redshift;
       defaultText = literalExpression "pkgs.redshift";
-      description = ''
+      description = lib.mdDoc ''
         redshift derivation to use.
       '';
     };
@@ -86,7 +86,7 @@ in {
       type = types.str;
       default = "/bin/redshift";
       example = "/bin/redshift-gtk";
-      description = ''
+      description = lib.mdDoc ''
         Redshift executable to use within the package.
       '';
     };
@@ -95,9 +95,9 @@ in {
       type = types.listOf types.str;
       default = [];
       example = [ "-v" "-m randr" ];
-      description = ''
+      description = lib.mdDoc ''
         Additional command-line arguments to pass to
-        <command>redshift</command>.
+        {command}`redshift`.
       '';
     };
   };
diff --git a/nixos/modules/services/x11/touchegg.nix b/nixos/modules/services/x11/touchegg.nix
index 9d3678e7696d..f1103c054c57 100644
--- a/nixos/modules/services/x11/touchegg.nix
+++ b/nixos/modules/services/x11/touchegg.nix
@@ -11,13 +11,13 @@ 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;
       default = pkgs.touchegg;
       defaultText = literalExpression "pkgs.touchegg";
-      description = "touchegg derivation to use.";
+      description = lib.mdDoc "touchegg derivation to use.";
     };
   };
 
diff --git a/nixos/modules/services/x11/unclutter-xfixes.nix b/nixos/modules/services/x11/unclutter-xfixes.nix
index 0b4d06f640d2..4a35176c5833 100644
--- a/nixos/modules/services/x11/unclutter-xfixes.nix
+++ b/nixos/modules/services/x11/unclutter-xfixes.nix
@@ -8,32 +8,32 @@ in {
   options.services.unclutter-xfixes = {
 
     enable = mkOption {
-      description = "Enable unclutter-xfixes to hide your mouse cursor when inactive.";
+      description = lib.mdDoc "Enable unclutter-xfixes to hide your mouse cursor when inactive.";
       type = types.bool;
       default = false;
     };
 
     package = mkOption {
-      description = "unclutter-xfixes derivation to use.";
+      description = lib.mdDoc "unclutter-xfixes derivation to use.";
       type = types.package;
       default = pkgs.unclutter-xfixes;
       defaultText = literalExpression "pkgs.unclutter-xfixes";
     };
 
     timeout = mkOption {
-      description = "Number of seconds before the cursor is marked inactive.";
+      description = lib.mdDoc "Number of seconds before the cursor is marked inactive.";
       type = types.int;
       default = 1;
     };
 
     threshold = mkOption {
-      description = "Minimum number of pixels considered cursor movement.";
+      description = lib.mdDoc "Minimum number of pixels considered cursor movement.";
       type = types.int;
       default = 1;
     };
 
     extraOptions = mkOption {
-      description = "More arguments to pass to the unclutter-xfixes command.";
+      description = lib.mdDoc "More arguments to pass to the unclutter-xfixes command.";
       type = types.listOf types.str;
       default = [];
       example = [ "exclude-root" "ignore-scrolling" "fork" ];
diff --git a/nixos/modules/services/x11/unclutter.nix b/nixos/modules/services/x11/unclutter.nix
index bdb5fa7b50cd..039214a575a7 100644
--- a/nixos/modules/services/x11/unclutter.nix
+++ b/nixos/modules/services/x11/unclutter.nix
@@ -8,7 +8,7 @@ in {
   options.services.unclutter = {
 
     enable = mkOption {
-      description = "Enable unclutter to hide your mouse cursor when inactive";
+      description = lib.mdDoc "Enable unclutter to hide your mouse cursor when inactive";
       type = types.bool;
       default = false;
     };
@@ -17,36 +17,36 @@ in {
       type = types.package;
       default = pkgs.unclutter;
       defaultText = literalExpression "pkgs.unclutter";
-      description = "unclutter derivation to use.";
+      description = lib.mdDoc "unclutter derivation to use.";
     };
 
     keystroke = mkOption {
-      description = "Wait for a keystroke before hiding the cursor";
+      description = lib.mdDoc "Wait for a keystroke before hiding the cursor";
       type = types.bool;
       default = false;
     };
 
     timeout = mkOption {
-      description = "Number of seconds before the cursor is marked inactive";
+      description = lib.mdDoc "Number of seconds before the cursor is marked inactive";
       type = types.int;
       default = 1;
     };
 
     threshold = mkOption {
-      description = "Minimum number of pixels considered cursor movement";
+      description = lib.mdDoc "Minimum number of pixels considered cursor movement";
       type = types.int;
       default = 1;
     };
 
     excluded = mkOption {
-      description = "Names of windows where unclutter should not apply";
+      description = lib.mdDoc "Names of windows where unclutter should not apply";
       type = types.listOf types.str;
       default = [];
       example = [ "" ];
     };
 
     extraOptions = mkOption {
-      description = "More arguments to pass to the unclutter command";
+      description = lib.mdDoc "More arguments to pass to the unclutter command";
       type = types.listOf types.str;
       default = [];
       example = [ "noevent" "grab" ];
diff --git a/nixos/modules/services/x11/urserver.nix b/nixos/modules/services/x11/urserver.nix
index 0beb62eb766a..d0b6e0775e5d 100644
--- a/nixos/modules/services/x11/urserver.nix
+++ b/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/nixos/modules/services/x11/urxvtd.nix b/nixos/modules/services/x11/urxvtd.nix
index 0a0df447f4e1..fedcb6c7293e 100644
--- a/nixos/modules/services/x11/urxvtd.nix
+++ b/nixos/modules/services/x11/urxvtd.nix
@@ -11,7 +11,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable urxvtd, the urxvt terminal daemon. To use urxvtd, run
         "urxvtc".
       '';
@@ -20,7 +20,7 @@ in {
     package = mkOption {
       default = pkgs.rxvt-unicode;
       defaultText = literalExpression "pkgs.rxvt-unicode";
-      description = ''
+      description = lib.mdDoc ''
         Package to install. Usually pkgs.rxvt-unicode.
       '';
       type = types.package;
diff --git a/nixos/modules/services/x11/window-managers/2bwm.nix b/nixos/modules/services/x11/window-managers/2bwm.nix
index fdbdf35b0f5a..8483a74b9f6c 100644
--- a/nixos/modules/services/x11/window-managers/2bwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/afterstep.nix b/nixos/modules/services/x11/window-managers/afterstep.nix
index ba88a64c702a..a06063597971 100644
--- a/nixos/modules/services/x11/window-managers/afterstep.nix
+++ b/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/nixos/modules/services/x11/window-managers/awesome.nix b/nixos/modules/services/x11/window-managers/awesome.nix
index c6c0c934f9ae..c1231d3fbf38 100644
--- a/nixos/modules/services/x11/window-managers/awesome.nix
+++ b/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,26 +21,26 @@ in
 
     services.xserver.windowManager.awesome = {
 
-      enable = mkEnableOption "Awesome window manager";
+      enable = mkEnableOption (lib.mdDoc "Awesome window manager");
 
       luaModules = mkOption {
         default = [];
         type = types.listOf types.package;
-        description = "List of lua packages available for being used in the Awesome configuration.";
+        description = lib.mdDoc "List of lua packages available for being used in the Awesome configuration.";
         example = literalExpression "[ pkgs.luaPackages.vicious ]";
       };
 
       package = mkOption {
         default = null;
         type = types.nullOr types.package;
-        description = "Package to use for running the Awesome WM.";
+        description = lib.mdDoc "Package to use for running the Awesome WM.";
         apply = pkg: if pkg == null then pkgs.awesome else pkg;
       };
 
       noArgb = mkOption {
         default = false;
         type = types.bool;
-        description = "Disable client transparency support, which can be greatly detrimental to performance in some setups";
+        description = lib.mdDoc "Disable client transparency support, which can be greatly detrimental to performance in some setups";
       };
     };
 
diff --git a/nixos/modules/services/x11/window-managers/berry.nix b/nixos/modules/services/x11/window-managers/berry.nix
index 0d2285e7a60e..eb5528602677 100644
--- a/nixos/modules/services/x11/window-managers/berry.nix
+++ b/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/nixos/modules/services/x11/window-managers/bspwm.nix b/nixos/modules/services/x11/window-managers/bspwm.nix
index ade24061a069..c403f744cd43 100644
--- a/nixos/modules/services/x11/window-managers/bspwm.nix
+++ b/nixos/modules/services/x11/window-managers/bspwm.nix
@@ -9,14 +9,14 @@ in
 {
   options = {
     services.xserver.windowManager.bspwm = {
-      enable = mkEnableOption "bspwm";
+      enable = mkEnableOption (lib.mdDoc "bspwm");
 
       package = mkOption {
         type        = types.package;
         default     = pkgs.bspwm;
         defaultText = literalExpression "pkgs.bspwm";
         example     = literalExpression "pkgs.bspwm-unstable";
-        description = ''
+        description = lib.mdDoc ''
           bspwm package to use.
         '';
       };
@@ -24,7 +24,7 @@ in
         type        = with types; nullOr path;
         example     = literalExpression ''"''${pkgs.bspwm}/share/doc/bspwm/examples/bspwmrc"'';
         default     = null;
-        description = ''
+        description = lib.mdDoc ''
           Path to the bspwm configuration file.
           If null, $HOME/.config/bspwm/bspwmrc will be used.
         '';
@@ -36,7 +36,7 @@ in
           default     = pkgs.sxhkd;
           defaultText = literalExpression "pkgs.sxhkd";
           example     = literalExpression "pkgs.sxhkd-unstable";
-          description = ''
+          description = lib.mdDoc ''
             sxhkd package to use.
           '';
         };
@@ -44,7 +44,7 @@ in
           type        = with types; nullOr path;
           example     = literalExpression ''"''${pkgs.bspwm}/share/doc/bspwm/examples/sxhkdrc"'';
           default     = null;
-          description = ''
+          description = lib.mdDoc ''
             Path to the sxhkd configuration file.
             If null, $HOME/.config/sxhkd/sxhkdrc will be used.
           '';
diff --git a/nixos/modules/services/x11/window-managers/clfswm.nix b/nixos/modules/services/x11/window-managers/clfswm.nix
index 78772c799744..f2e4c2f91c9d 100644
--- a/nixos/modules/services/x11/window-managers/clfswm.nix
+++ b/nixos/modules/services/x11/window-managers/clfswm.nix
@@ -9,12 +9,12 @@ in
 {
   options = {
     services.xserver.windowManager.clfswm = {
-      enable = mkEnableOption "clfswm";
+      enable = mkEnableOption (lib.mdDoc "clfswm");
       package = mkOption {
         type        = types.package;
         default     = pkgs.lispPackages.clfswm;
         defaultText = literalExpression "pkgs.lispPackages.clfswm";
-        description = ''
+        description = lib.mdDoc ''
           clfswm package to use.
         '';
       };
diff --git a/nixos/modules/services/x11/window-managers/cwm.nix b/nixos/modules/services/x11/window-managers/cwm.nix
index 03375a226bb6..9a143e7bccc3 100644
--- a/nixos/modules/services/x11/window-managers/cwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/default.nix b/nixos/modules/services/x11/window-managers/default.nix
index d71738ea633f..48b413beaa86 100644
--- a/nixos/modules/services/x11/window-managers/default.nix
+++ b/nixos/modules/services/x11/window-managers/default.nix
@@ -19,8 +19,11 @@ in
     ./evilwm.nix
     ./exwm.nix
     ./fluxbox.nix
-    ./fvwm.nix
+    ./fvwm2.nix
+    ./fvwm3.nix
+    ./hackedbox.nix
     ./herbstluftwm.nix
+    ./hypr.nix
     ./i3.nix
     ./jwm.nix
     ./leftwm.nix
@@ -57,10 +60,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";
@@ -71,8 +74,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/nixos/modules/services/x11/window-managers/dwm.nix b/nixos/modules/services/x11/window-managers/dwm.nix
index 7777913ce1e6..1881826944aa 100644
--- a/nixos/modules/services/x11/window-managers/dwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/e16.nix b/nixos/modules/services/x11/window-managers/e16.nix
index 3e1a22c4dabd..000feea12c2c 100644
--- a/nixos/modules/services/x11/window-managers/e16.nix
+++ b/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/nixos/modules/services/x11/window-managers/evilwm.nix b/nixos/modules/services/x11/window-managers/evilwm.nix
index 6f1db2110f87..842f84c2cfbe 100644
--- a/nixos/modules/services/x11/window-managers/evilwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/exwm.nix b/nixos/modules/services/x11/window-managers/exwm.nix
index b505f720f04c..a97ed74ae881 100644
--- a/nixos/modules/services/x11/window-managers/exwm.nix
+++ b/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;
@@ -26,7 +26,7 @@ in
           (require 'exwm)
           (exwm-enable)
         '';
-        description = ''
+        description = lib.mdDoc ''
           Emacs lisp code to be run after loading the user's init
           file. If enableDefaultConfig is true, this will be run
           before loading the default config.
@@ -35,7 +35,7 @@ in
       enableDefaultConfig = mkOption {
         default = true;
         type = lib.types.bool;
-        description = "Enable an uncustomised exwm configuration.";
+        description = lib.mdDoc "Enable an uncustomised exwm configuration.";
       };
       extraPackages = mkOption {
         type = types.functionTo (types.listOf types.package);
@@ -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/nixos/modules/services/x11/window-managers/fluxbox.nix b/nixos/modules/services/x11/window-managers/fluxbox.nix
index b409335702af..24165fb6fb07 100644
--- a/nixos/modules/services/x11/window-managers/fluxbox.nix
+++ b/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/nixos/modules/services/x11/window-managers/fvwm.nix b/nixos/modules/services/x11/window-managers/fvwm.nix
deleted file mode 100644
index e283886ecc40..000000000000
--- a/nixos/modules/services/x11/window-managers/fvwm.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.xserver.windowManager.fvwm;
-  fvwm = pkgs.fvwm.override { enableGestures = cfg.gestures; };
-in
-
-{
-
-  ###### interface
-
-  options = {
-    services.xserver.windowManager.fvwm = {
-      enable = mkEnableOption "Fvwm window manager";
-
-      gestures = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Whether or not to enable libstroke for gesture support";
-      };
-    };
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    services.xserver.windowManager.session = singleton
-      { name = "fvwm";
-        start =
-          ''
-            ${fvwm}/bin/fvwm &
-            waitPID=$!
-          '';
-      };
-
-    environment.systemPackages = [ fvwm ];
-  };
-}
diff --git a/nixos/modules/services/x11/window-managers/fvwm2.nix b/nixos/modules/services/x11/window-managers/fvwm2.nix
new file mode 100644
index 000000000000..aaf3c5c46906
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/fvwm2.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.fvwm2;
+  fvwm2 = pkgs.fvwm2.override { enableGestures = cfg.gestures; };
+in
+
+{
+
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "xserver" "windowManager" "fvwm" ]
+      [ "services" "xserver" "windowManager" "fvwm2" ])
+  ];
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager.fvwm2 = {
+      enable = mkEnableOption (lib.mdDoc "Fvwm2 window manager");
+
+      gestures = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc "Whether or not to enable libstroke for gesture support";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      { name = "fvwm2";
+        start =
+          ''
+            ${fvwm2}/bin/fvwm &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ fvwm2 ];
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/fvwm3.nix b/nixos/modules/services/x11/window-managers/fvwm3.nix
new file mode 100644
index 000000000000..50c76b67eea3
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/fvwm3.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.fvwm3;
+  inherit (pkgs) fvwm3;
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager.fvwm3 = {
+      enable = mkEnableOption (lib.mdDoc "Fvwm3 window manager");
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      { name = "fvwm3";
+        start =
+          ''
+            ${fvwm3}/bin/fvwm3 &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ fvwm3 ];
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/hackedbox.nix b/nixos/modules/services/x11/window-managers/hackedbox.nix
new file mode 100644
index 000000000000..61e911961f51
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/hackedbox.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.hackedbox;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.hackedbox.enable = mkEnableOption (lib.mdDoc "hackedbox");
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "hackedbox";
+      start = ''
+        ${pkgs.hackedbox}/bin/hackedbox &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.hackedbox ];
+  };
+}
diff --git a/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
index 354d70c695cb..816cbb36cafd 100644
--- a/nixos/modules/services/x11/window-managers/herbstluftwm.nix
+++ b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
@@ -9,13 +9,13 @@ in
 {
   options = {
     services.xserver.windowManager.herbstluftwm = {
-      enable = mkEnableOption "herbstluftwm";
+      enable = mkEnableOption (lib.mdDoc "herbstluftwm");
 
       package = mkOption {
         type = types.package;
         default = pkgs.herbstluftwm;
         defaultText = literalExpression "pkgs.herbstluftwm";
-        description = ''
+        description = lib.mdDoc ''
           Herbstluftwm package to use.
         '';
       };
@@ -23,7 +23,7 @@ in
       configFile = mkOption {
         default     = null;
         type        = with types; nullOr path;
-        description = ''
+        description = lib.mdDoc ''
           Path to the herbstluftwm configuration file.  If left at the
           default value, $XDG_CONFIG_HOME/herbstluftwm/autostart will
           be used.
diff --git a/nixos/modules/services/x11/window-managers/hypr.nix b/nixos/modules/services/x11/window-managers/hypr.nix
new file mode 100644
index 000000000000..4c1fea71f93e
--- /dev/null
+++ b/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/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index 99f9997024fe..64109e0c39fd 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -8,12 +8,12 @@ in
 
 {
   options.services.xserver.windowManager.i3 = {
-    enable = mkEnableOption "i3 window manager";
+    enable = mkEnableOption (lib.mdDoc "i3 window manager");
 
     configFile = mkOption {
       default     = null;
       type        = with types; nullOr path;
-      description = ''
+      description = lib.mdDoc ''
         Path to the i3 configuration file.
         If left at the default value, $HOME/.i3/config will be used.
       '';
@@ -22,7 +22,7 @@ in
     extraSessionCommands = mkOption {
       default     = "";
       type        = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands executed just before i3 is started.
       '';
     };
@@ -32,7 +32,7 @@ in
       default     = pkgs.i3;
       defaultText = literalExpression "pkgs.i3";
       example     = literalExpression "pkgs.i3-gaps";
-      description = ''
+      description = lib.mdDoc ''
         i3 package to use.
       '';
     };
@@ -47,7 +47,7 @@ in
           i3lock
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra packages to be installed system wide.
       '';
     };
diff --git a/nixos/modules/services/x11/window-managers/icewm.nix b/nixos/modules/services/x11/window-managers/icewm.nix
index f4ae9222df67..48741aa41d85 100644
--- a/nixos/modules/services/x11/window-managers/icewm.nix
+++ b/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/nixos/modules/services/x11/window-managers/jwm.nix b/nixos/modules/services/x11/window-managers/jwm.nix
index 0e8dab2e9224..40758029bc65 100644
--- a/nixos/modules/services/x11/window-managers/jwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/katriawm.nix b/nixos/modules/services/x11/window-managers/katriawm.nix
new file mode 100644
index 000000000000..106631792ff4
--- /dev/null
+++ b/nixos/modules/services/x11/window-managers/katriawm.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mdDoc mkEnableOption mkIf mkPackageOption singleton;
+  cfg = config.services.xserver.windowManager.katriawm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.katriawm = {
+      enable = mkEnableOption (mdDoc "katriawm");
+      package = mkPackageOption 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/nixos/modules/services/x11/window-managers/leftwm.nix b/nixos/modules/services/x11/window-managers/leftwm.nix
index 3ef40df95df2..2571735ba8bf 100644
--- a/nixos/modules/services/x11/window-managers/leftwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/lwm.nix b/nixos/modules/services/x11/window-managers/lwm.nix
index e2aa062fd13b..517abb23d4af 100644
--- a/nixos/modules/services/x11/window-managers/lwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/metacity.nix b/nixos/modules/services/x11/window-managers/metacity.nix
index 600afe759b2c..1f69147af5bc 100644
--- a/nixos/modules/services/x11/window-managers/metacity.nix
+++ b/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/nixos/modules/services/x11/window-managers/mlvwm.nix b/nixos/modules/services/x11/window-managers/mlvwm.nix
index 08dd04020296..fe0433c24b60 100644
--- a/nixos/modules/services/x11/window-managers/mlvwm.nix
+++ b/nixos/modules/services/x11/window-managers/mlvwm.nix
@@ -8,12 +8,12 @@ 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;
       type = with types; nullOr path;
-      description = ''
+      description = lib.mdDoc ''
         Path to the mlvwm configuration file.
         If left at the default value, $HOME/.mlvwmrc will be used.
       '';
diff --git a/nixos/modules/services/x11/window-managers/mwm.nix b/nixos/modules/services/x11/window-managers/mwm.nix
index 31f7b725f747..9f8dc0939e5e 100644
--- a/nixos/modules/services/x11/window-managers/mwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/notion.nix b/nixos/modules/services/x11/window-managers/notion.nix
index 4ece0d241c90..0015e90a41c5 100644
--- a/nixos/modules/services/x11/window-managers/notion.nix
+++ b/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/nixos/modules/services/x11/window-managers/openbox.nix b/nixos/modules/services/x11/window-managers/openbox.nix
index 165772d1aa09..bf5a500f431a 100644
--- a/nixos/modules/services/x11/window-managers/openbox.nix
+++ b/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/nixos/modules/services/x11/window-managers/oroborus.nix b/nixos/modules/services/x11/window-managers/oroborus.nix
index bd7e3396864b..654b8708e48f 100644
--- a/nixos/modules/services/x11/window-managers/oroborus.nix
+++ b/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/nixos/modules/services/x11/window-managers/pekwm.nix b/nixos/modules/services/x11/window-managers/pekwm.nix
index 850335ce7ddf..8818f568647a 100644
--- a/nixos/modules/services/x11/window-managers/pekwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix
index 4d455fdf7b2d..523642591d94 100644
--- a/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixos/modules/services/x11/window-managers/qtile.nix
@@ -8,7 +8,7 @@ in
 
 {
   options.services.xserver.windowManager.qtile = {
-    enable = mkEnableOption "qtile";
+    enable = mkEnableOption (lib.mdDoc "qtile");
 
     package = mkPackageOption pkgs "qtile" { };
   };
diff --git a/nixos/modules/services/x11/window-managers/ratpoison.nix b/nixos/modules/services/x11/window-managers/ratpoison.nix
index 0d58481d4579..1de0fad3e54d 100644
--- a/nixos/modules/services/x11/window-managers/ratpoison.nix
+++ b/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/nixos/modules/services/x11/window-managers/sawfish.nix b/nixos/modules/services/x11/window-managers/sawfish.nix
index b988b5e1829e..1945a1af6763 100644
--- a/nixos/modules/services/x11/window-managers/sawfish.nix
+++ b/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/nixos/modules/services/x11/window-managers/smallwm.nix b/nixos/modules/services/x11/window-managers/smallwm.nix
index 091ba4f92b94..e92b18690d8a 100644
--- a/nixos/modules/services/x11/window-managers/smallwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/spectrwm.nix b/nixos/modules/services/x11/window-managers/spectrwm.nix
index a1dc298d2426..c464803a0b6a 100644
--- a/nixos/modules/services/x11/window-managers/spectrwm.nix
+++ b/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/nixos/modules/services/x11/window-managers/stumpwm.nix b/nixos/modules/services/x11/window-managers/stumpwm.nix
index 27a17178476a..162af689dbba 100644
--- a/nixos/modules/services/x11/window-managers/stumpwm.nix
+++ b/nixos/modules/services/x11/window-managers/stumpwm.nix
@@ -8,7 +8,7 @@ in
 
 {
   options = {
-    services.xserver.windowManager.stumpwm.enable = mkEnableOption "stumpwm";
+    services.xserver.windowManager.stumpwm.enable = mkEnableOption (lib.mdDoc "stumpwm");
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/x11/window-managers/tinywm.nix b/nixos/modules/services/x11/window-managers/tinywm.nix
index 8e5d9b9170ca..7418a6ddc760 100644
--- a/nixos/modules/services/x11/window-managers/tinywm.nix
+++ b/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/nixos/modules/services/x11/window-managers/twm.nix b/nixos/modules/services/x11/window-managers/twm.nix
index fc09901aae3b..231817a26e66 100644
--- a/nixos/modules/services/x11/window-managers/twm.nix
+++ b/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/nixos/modules/services/x11/window-managers/windowlab.nix b/nixos/modules/services/x11/window-managers/windowlab.nix
index fb891a39fa41..9a0646b6ee7d 100644
--- a/nixos/modules/services/x11/window-managers/windowlab.nix
+++ b/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/nixos/modules/services/x11/window-managers/windowmaker.nix b/nixos/modules/services/x11/window-managers/windowmaker.nix
index b62723758056..a679e2b5bc80 100644
--- a/nixos/modules/services/x11/window-managers/windowmaker.nix
+++ b/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/nixos/modules/services/x11/window-managers/wmderland.nix b/nixos/modules/services/x11/window-managers/wmderland.nix
index 56b692209651..ed515741f62e 100644
--- a/nixos/modules/services/x11/window-managers/wmderland.nix
+++ b/nixos/modules/services/x11/window-managers/wmderland.nix
@@ -8,12 +8,12 @@ in
 
 {
   options.services.xserver.windowManager.wmderland = {
-    enable = mkEnableOption "wmderland";
+    enable = mkEnableOption (lib.mdDoc "wmderland");
 
     extraSessionCommands = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands executed just before wmderland is started.
       '';
     };
@@ -38,7 +38,7 @@ in
           rxvt-unicode
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra packages to be installed system wide.
       '';
     };
diff --git a/nixos/modules/services/x11/window-managers/wmii.nix b/nixos/modules/services/x11/window-managers/wmii.nix
index 9b50a99bf23f..090aa31610ab 100644
--- a/nixos/modules/services/x11/window-managers/wmii.nix
+++ b/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/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index 68f97c2f504b..c35446bf405b 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -30,7 +30,7 @@ let
         install -D ${xmonadEnv}/share/man/man1/xmonad.1.gz $out/share/man/man1/xmonad.1.gz
         makeWrapper ${configured}/bin/xmonad $out/bin/xmonad \
       '' + optionalString cfg.enableConfiguredRecompile ''
-          --set NIX_GHC "${xmonadEnv}/bin/ghc" \
+          --set XMONAD_GHC "${xmonadEnv}/bin/ghc" \
       '' + ''
           --set XMONAD_XMESSAGE "${pkgs.xorg.xmessage}/bin/xmessage"
       '');
@@ -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.ghc784";
+        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,23 +66,23 @@ 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.
         '';
       };
 
       enableContribAndExtras = mkOption {
         default = false;
         type = lib.types.bool;
-        description = "Enable xmonad-{contrib,extras} in Xmonad.";
+        description = lib.mdDoc "Enable xmonad-{contrib,extras} in Xmonad.";
       };
 
       config = mkOption {
         default = null;
         type = with lib.types; nullOr (either path str);
-        description = ''
+        description = lib.mdDoc ''
           Configuration from which XMonad gets compiled. If no value is
           specified, a vanilla xmonad binary is put in PATH, which will
           attempt to recompile and exec your xmonad config from $HOME/.xmonad.
@@ -94,17 +94,17 @@ in {
           "mod+q" restart key binding dysfunctional though, because that attempts
           to call your binary with the "--restart" command line option, unless
           you implement that yourself. You way mant to bind "mod+q" to
-          <literal>(restart "xmonad" True)</literal> instead, which will just restart
+          `(restart "xmonad" True)` instead, which will just restart
           xmonad from PATH. This allows e.g. switching to the new xmonad binary
           after rebuilding your system with nixos-rebuild.
           For the same reason, ghc is not added to the environment when this
-          option is set, unless <option>enableConfiguredRecompile</option> is
-          set to <literal>true</literal>.
+          option is set, unless {option}`enableConfiguredRecompile` is
+          set to `true`.
 
           If you actually want to run xmonad with a config specified here, but
           also be able to recompile and restart it from a copy of that source in
-          $HOME/.xmonad on the fly, set <option>enableConfiguredRecompile</option>
-          to <literal>true</literal> and implement something like "compileRestart"
+          $HOME/.xmonad on the fly, set {option}`enableConfiguredRecompile`
+          to `true` and implement something like "compileRestart"
           from the example.
           This should allow you to switch at will between the local xmonad and
           the one NixOS puts in your PATH.
@@ -128,41 +128,42 @@ in {
             [ ( (mod4Mask,xK_r), compileRestart True)
             , ( (mod4Mask,xK_q), restart "xmonad" True ) ]
 
+          compileRestart resume = do
+            dirs  <- asks directories
+            whenX (recompile dirs True) $ do
+              when resume writeStateToFile
+              catchIO
+                  ( do
+                      args <- getArgs
+                      executeFile (cacheDir dirs </> compiledConfig) False args Nothing
+                  )
+
+          main = getDirectories >>= launch myConfig
+
           --------------------------------------------
-          {- version 0.17.0 -}
+          {- For versions before 0.17.0 use this instead -}
           --------------------------------------------
           -- compileRestart resume =
-          --   dirs <- io getDirectories
-          --   whenX (recompile dirs True) $
+          --   whenX (recompile True) $
           --     when resume writeStateToFile
           --       *> catchIO
           --         ( do
+          --             dir <- getXMonadDataDir
           --             args <- getArgs
-          --             executeFile (cacheDir dirs </> compiledConfig) False args Nothing
+          --             executeFile (dir </> compiledConfig) False args Nothing
           --         )
           --
-          -- main = getDirectories >>= launch myConfig
+          -- main = launch myConfig
           --------------------------------------------
 
-          compileRestart resume =
-            whenX (recompile True) $
-              when resume writeStateToFile
-                *> catchIO
-                  ( do
-                      dir <- getXMonadDataDir
-                      args <- getArgs
-                      executeFile (dir </> compiledConfig) False args Nothing
-                  )
-
-          main = launch myConfig
         '';
       };
 
       enableConfiguredRecompile = mkOption {
         default = false;
         type = lib.types.bool;
-        description = ''
-          Enable recompilation even if <option>config</option> is set to a
+        description = lib.mdDoc ''
+          Enable recompilation even if {option}`config` is set to a
           non-null value. This adds the necessary Haskell dependencies (GHC with
           packages) to the xmonad binary's environment.
         '';
@@ -171,7 +172,7 @@ in {
       xmonadCliArgs = mkOption {
         default = [];
         type = with lib.types; listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Command line arguments passed to the xmonad binary.
         '';
       };
@@ -179,7 +180,7 @@ in {
       ghcArgs = mkOption {
         default = [];
         type = with lib.types; listOf str;
-        description = ''
+        description = lib.mdDoc ''
           Command line arguments passed to the compiler (ghc)
           invocation when xmonad.config is set.
         '';
diff --git a/nixos/modules/services/x11/window-managers/yeahwm.nix b/nixos/modules/services/x11/window-managers/yeahwm.nix
index 351bd7dfe48b..9b40cecace26 100644
--- a/nixos/modules/services/x11/window-managers/yeahwm.nix
+++ b/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/nixos/modules/services/x11/xautolock.nix b/nixos/modules/services/x11/xautolock.nix
index 947d8f4edfb5..5b8b748a086b 100644
--- a/nixos/modules/services/x11/xautolock.nix
+++ b/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.
           '';
@@ -20,7 +20,7 @@ in
           default = 15;
           type = types.int;
 
-          description = ''
+          description = lib.mdDoc ''
             Idle time (in minutes) to wait until xautolock locks the computer.
           '';
         };
@@ -31,7 +31,7 @@ in
           example = literalExpression ''"''${pkgs.i3lock}/bin/i3lock -i /path/to/img"'';
           type = types.str;
 
-          description = ''
+          description = lib.mdDoc ''
             The script to use when automatically locking the computer.
           '';
         };
@@ -41,8 +41,8 @@ in
           example = literalExpression ''"''${pkgs.i3lock}/bin/i3lock -i /path/to/img"'';
           type = types.nullOr types.str;
 
-          description = ''
-            The script to use when manually locking the computer with <command>xautolock -locknow</command>.
+          description = lib.mdDoc ''
+            The script to use when manually locking the computer with {command}`xautolock -locknow`.
           '';
         };
 
@@ -50,7 +50,7 @@ in
           default = 10;
           type = types.int;
 
-          description = ''
+          description = lib.mdDoc ''
             Time (in seconds) before the actual lock when the notification about the pending lock should be published.
           '';
         };
@@ -60,7 +60,7 @@ in
           example = literalExpression ''"''${pkgs.libnotify}/bin/notify-send 'Locking in 10 seconds'"'';
           type = types.nullOr types.str;
 
-          description = ''
+          description = lib.mdDoc ''
             Notification script to be used to warn about the pending autolock.
           '';
         };
@@ -70,8 +70,8 @@ in
           example = "/run/current-system/systemd/bin/systemctl suspend";
           type = types.nullOr types.str;
 
-          description = ''
-            The script to use when nothing has happend for as long as <option>killtime</option>
+          description = lib.mdDoc ''
+            The script to use when nothing has happened for as long as {option}`killtime`
           '';
         };
 
@@ -79,8 +79,8 @@ in
           default = 20; # default according to `man xautolock`
           type = types.int;
 
-          description = ''
-            Minutes xautolock waits until it executes the script specified in <option>killer</option>
+          description = lib.mdDoc ''
+            Minutes xautolock waits until it executes the script specified in {option}`killer`
             (Has to be at least 10 minutes)
           '';
         };
@@ -89,9 +89,9 @@ in
           type = types.listOf types.str;
           default = [ ];
           example = [ "-detectsleep" ];
-          description = ''
+          description = lib.mdDoc ''
             Additional command-line arguments to pass to
-            <command>xautolock</command>.
+            {command}`xautolock`.
           '';
         };
       };
diff --git a/nixos/modules/services/x11/xbanish.nix b/nixos/modules/services/x11/xbanish.nix
index b95fac68f165..de893fae75a1 100644
--- a/nixos/modules/services/x11/xbanish.nix
+++ b/nixos/modules/services/x11/xbanish.nix
@@ -7,10 +7,10 @@ let cfg = config.services.xbanish;
 in {
   options.services.xbanish = {
 
-    enable = mkEnableOption "xbanish";
+    enable = mkEnableOption (lib.mdDoc "xbanish");
 
     arguments = mkOption {
-      description = "Arguments to pass to xbanish command";
+      description = lib.mdDoc "Arguments to pass to xbanish command";
       default = "";
       example = "-d -i shift";
       type = types.str;
diff --git a/nixos/modules/services/x11/xfs.nix b/nixos/modules/services/x11/xfs.nix
index ea7cfa1aa43c..591bf461496e 100644
--- a/nixos/modules/services/x11/xfs.nix
+++ b/nixos/modules/services/x11/xfs.nix
@@ -19,7 +19,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the X Font Server.";
+        description = lib.mdDoc "Whether to enable the X Font Server.";
       };
 
     };
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index d488e9b55d43..83a71dcf23e0 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -37,18 +37,16 @@ let
     output = mkOption {
       type = types.str;
       example = "DVI-0";
-      description = ''
-        The output name of the monitor, as shown by <citerefentry>
-          <refentrytitle>xrandr</refentrytitle>
-          <manvolnum>1</manvolnum>
-        </citerefentry> invoked without arguments.
+      description = lib.mdDoc ''
+        The output name of the monitor, as shown by
+        {manpage}`xrandr(1)` invoked without arguments.
       '';
     };
 
     primary = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether this head is treated as the primary monitor,
       '';
     };
@@ -60,11 +58,10 @@ let
         DisplaySize 408 306
         Option "DPMS" "false"
       '';
-      description = ''
-        Extra lines to append to the <literal>Monitor</literal> section
+      description = lib.mdDoc ''
+        Extra lines to append to the `Monitor` section
         verbatim. Available options are documented in the MONITOR section in
-        <citerefentry><refentrytitle>xorg.conf</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry>.
+        {manpage}`xorg.conf(5)`.
       '';
     };
   };
@@ -154,8 +151,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.")
     ];
 
 
@@ -168,7 +167,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the X server.
         '';
       };
@@ -176,7 +175,7 @@ in
       autorun = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to start the X server automatically.
         '';
       };
@@ -185,22 +184,22 @@ in
         default = [];
         example = literalExpression "[ pkgs.xterm ]";
         type = types.listOf types.package;
-        description = "Which X11 packages to exclude from the default environment";
+        description = lib.mdDoc "Which X11 packages to exclude from the default environment";
       };
 
       exportConfiguration = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to symlink the X server configuration under
-          <filename>/etc/X11/xorg.conf</filename>.
+          {file}`/etc/X11/xorg.conf`.
         '';
       };
 
       enableTCP = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to allow the X server to accept TCP connections.
         '';
       };
@@ -208,7 +207,7 @@ in
       autoRepeatDelay = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Sets the autorepeat delay (length of time in milliseconds that a key must be depressed before autorepeat starts).
         '';
       };
@@ -216,7 +215,7 @@ in
       autoRepeatInterval = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Sets the autorepeat interval (length of time in milliseconds that should elapse between autorepeat-generated keystrokes).
         '';
       };
@@ -234,21 +233,21 @@ in
             '''
           ]
         '';
-        description = "Content of additional InputClass sections of the X server configuration file.";
+        description = lib.mdDoc "Content of additional InputClass sections of the X server configuration file.";
       };
 
       modules = mkOption {
         type = types.listOf types.path;
         default = [];
         example = literalExpression "[ pkgs.xf86_input_wacom ]";
-        description = "Packages to be added to the module search path of the X server.";
+        description = lib.mdDoc "Packages to be added to the module search path of the X server.";
       };
 
       resolutions = mkOption {
         type = types.listOf types.attrs;
         default = [];
         example = [ { x = 1600; y = 1200; } { x = 1024; y = 786; } ];
-        description = ''
+        description = lib.mdDoc ''
           The screen resolutions for the X server.  The first element
           is the default resolution.  If this list is empty, the X
           server will automatically configure the resolution.
@@ -269,7 +268,7 @@ in
               path  = [ "xorg" n ];
               title = removePrefix "xf86video" n;
             }) pkgs.xorg);
-        description = ''
+        description = lib.mdDoc ''
           The names of the video drivers the configuration
           supports. They will be tried in order until one that
           supports your card is found.
@@ -285,17 +284,17 @@ in
         type = types.nullOr types.str;
         default = null;
         example = "i810";
-        description = ''
+        description = lib.mdDoc ''
           The name of the video driver for your graphics card.  This
           option is obsolete; please set the
-          <option>services.xserver.videoDrivers</option> instead.
+          {option}`services.xserver.videoDrivers` instead.
         '';
       };
 
       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.
         '';
@@ -304,17 +303,17 @@ in
       dpi = mkOption {
         type = types.nullOr types.int;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Force global DPI resolution to use for X server. It's recommended to
           use this only when DPI is detected incorrectly; also consider using
-          <literal>Monitor</literal> section in configuration file instead.
+          `Monitor` section in configuration file instead.
         '';
       };
 
       updateDbusEnvironment = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to update the DBus activation environment after launching the
           desktop manager.
         '';
@@ -323,7 +322,7 @@ in
       layout = mkOption {
         type = types.str;
         default = "us";
-        description = ''
+        description = lib.mdDoc ''
           Keyboard layout, or multiple keyboard layouts separated by commas.
         '';
       };
@@ -332,7 +331,7 @@ in
         type = types.str;
         default = "pc104";
         example = "presario";
-        description = ''
+        description = lib.mdDoc ''
           Keyboard model.
         '';
       };
@@ -341,7 +340,7 @@ in
         type = types.commas;
         default = "terminate:ctrl_alt_bksp";
         example = "grp:caps_toggle,grp_led:scroll";
-        description = ''
+        description = lib.mdDoc ''
           X keyboard options; layout switching goes here.
         '';
       };
@@ -350,7 +349,7 @@ in
         type = types.str;
         default = "";
         example = "colemak";
-        description = ''
+        description = lib.mdDoc ''
           X keyboard variant.
         '';
       };
@@ -359,22 +358,22 @@ in
         type = types.path;
         default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
         defaultText = literalExpression ''"''${pkgs.xkeyboard_config}/etc/X11/xkb"'';
-        description = ''
+        description = lib.mdDoc ''
           Path used for -xkbdir xserver parameter.
         '';
       };
 
       config = mkOption {
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           The contents of the configuration file of the X server
-          (<filename>xorg.conf</filename>).
+          ({file}`xorg.conf`).
 
           This option is set by multiple modules, and the configs are
           concatenated together.
 
           In Xorg configs the last config entries take precedence,
-          so you may want to use <literal>lib.mkAfter</literal> on this option
+          so you may want to use `lib.mkAfter` on this option
           to override NixOS's defaults.
         '';
       };
@@ -383,14 +382,14 @@ in
         type = types.lines;
         default = "";
         example = ''FontPath "/path/to/my/fonts"'';
-        description = "Contents of the first <literal>Files</literal> section of the X server configuration file.";
+        description = lib.mdDoc "Contents of the first `Files` section of the X server configuration file.";
       };
 
       deviceSection = mkOption {
         type = types.lines;
         default = "";
         example = "VideoRAM 131072";
-        description = "Contents of the first Device section of the X server configuration file.";
+        description = lib.mdDoc "Contents of the first Device section of the X server configuration file.";
       };
 
       screenSection = mkOption {
@@ -399,20 +398,20 @@ in
         example = ''
           Option "RandRRotation" "on"
         '';
-        description = "Contents of the first Screen section of the X server configuration file.";
+        description = lib.mdDoc "Contents of the first Screen section of the X server configuration file.";
       };
 
       monitorSection = mkOption {
         type = types.lines;
         default = "";
         example = "HorizSync 28-49";
-        description = "Contents of the first Monitor section of the X server configuration file.";
+        description = lib.mdDoc "Contents of the first Monitor section of the X server configuration file.";
       };
 
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Additional contents (sections) included in the X server configuration file";
+        description = lib.mdDoc "Additional contents (sections) included in the X server configuration file";
       };
 
       xrandrHeads = mkOption {
@@ -432,23 +431,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
@@ -466,7 +467,7 @@ in
           Option "SuspendTime" "0"
           Option "OffTime" "0"
           '';
-        description = "Contents of the ServerFlags section of the X server configuration file.";
+        description = lib.mdDoc "Contents of the ServerFlags section of the X server configuration file.";
       };
 
       moduleSection = mkOption {
@@ -477,7 +478,7 @@ in
             SubSection "extmod"
             EndSubsection
           '';
-        description = "Contents of the Module section of the X server configuration file.";
+        description = lib.mdDoc "Contents of the Module section of the X server configuration file.";
       };
 
       serverLayoutSection = mkOption {
@@ -487,28 +488,28 @@ in
           ''
             Option "AIGLX" "true"
           '';
-        description = "Contents of the ServerLayout section of the X server configuration file.";
+        description = lib.mdDoc "Contents of the ServerLayout section of the X server configuration file.";
       };
 
       extraDisplaySettings = mkOption {
         type = types.lines;
         default = "";
         example = "Virtual 2048 2048";
-        description = "Lines to be added to every Display subsection of the Screen section.";
+        description = lib.mdDoc "Lines to be added to every Display subsection of the Screen section.";
       };
 
       defaultDepth = mkOption {
         type = types.int;
         default = 0;
         example = 8;
-        description = "Default colour depth.";
+        description = lib.mdDoc "Default colour depth.";
       };
 
       fontPath = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "unix/:7100";
-        description = ''
+        description = lib.mdDoc ''
           Set the X server FontPath. Defaults to null, which
           means the compiled in defaults will be used. See
           man xorg.conf for details.
@@ -518,20 +519,20 @@ in
       tty = mkOption {
         type = types.nullOr types.int;
         default = 7;
-        description = "Virtual console for the X server.";
+        description = lib.mdDoc "Virtual console for the X server.";
       };
 
       display = mkOption {
         type = types.nullOr types.int;
         default = 0;
-        description = "Display number for the X server.";
+        description = lib.mdDoc "Display number for the X server.";
       };
 
       virtualScreen = mkOption {
         type = types.nullOr types.attrs;
         default = null;
         example = { x = 2048; y = 2048; };
-        description = ''
+        description = lib.mdDoc ''
           Virtual screen size for Xrandr.
         '';
       };
@@ -540,12 +541,12 @@ in
         type = types.nullOr types.str;
         default = "/dev/null";
         example = "/var/log/Xorg.0.log";
-        description = ''
+        description = lib.mdDoc ''
           Controls the file Xorg logs to.
 
-          The default of <literal>/dev/null</literal> is set so that systemd services (like <literal>displayManagers</literal>) only log to the journal and don't create their own log files.
+          The default of `/dev/null` is set so that systemd services (like `displayManagers`) only log to the journal and don't create their own log files.
 
-          Setting this to <literal>null</literal> will not pass the <literal>-logfile</literal> argument to Xorg which allows it to log to its default logfile locations instead (see <literal>man Xorg</literal>). You probably only want this behaviour when running Xorg manually (e.g. via <literal>startx</literal>).
+          Setting this to `null` will not pass the `-logfile` argument to Xorg which allows it to log to its default logfile locations instead (see `man Xorg`). You probably only want this behaviour when running Xorg manually (e.g. via `startx`).
         '';
       };
 
@@ -553,24 +554,15 @@ in
         type = types.nullOr types.int;
         default = 3;
         example = 7;
-        description = ''
+        description = lib.mdDoc ''
           Controls verbosity of X logging.
         '';
       };
 
-      useGlamor = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to use the Glamor module for 2D acceleration,
-          if possible.
-        '';
-      };
-
       enableCtrlAltBackspace = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the DontZap option, which binds Ctrl+Alt+Backspace
           to forcefully kill X. This can lead to data loss and is disabled
           by default.
@@ -580,7 +572,7 @@ in
       terminateOnReset = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Whether to terminate X upon server reset.
         '';
       };
@@ -797,13 +789,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: ''
@@ -811,7 +796,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}
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index c04d0fc16b24..ddb165a76cc8 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/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.
             '';
           };
@@ -123,12 +123,12 @@ in
         }
       '';
 
-      description = ''
+      description = lib.mdDoc ''
         A set of shell script fragments that are executed when a NixOS
         system configuration is activated.  Examples are updating
         /etc, creating accounts, and so on.  Since these are executed
         every time you boot the system or run
-        <command>nixos-rebuild</command>, it's important that they are
+        {command}`nixos-rebuild`, it's important that they are
         idempotent and fast.
       '';
 
@@ -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 {
@@ -159,12 +159,12 @@ in
         }
       '';
 
-      description = ''
+      description = lib.mdDoc ''
         A set of shell script fragments that are executed by a systemd user
         service when a NixOS system configuration is activated. Examples are
         rebuilding the .desktop file cache for showing applications in the menu.
         Since these are executed every time you run
-        <command>nixos-rebuild</command>, it's important that they are
+        {command}`nixos-rebuild`, it's important that they are
         idempotent and fast.
       '';
 
@@ -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`.
       '';
     };
   };
diff --git a/nixos/modules/system/activation/bootspec.cue b/nixos/modules/system/activation/bootspec.cue
new file mode 100644
index 000000000000..9f857a1b1cd8
--- /dev/null
+++ b/nixos/modules/system/activation/bootspec.cue
@@ -0,0 +1,18 @@
+#V1: {
+	system:         string
+	init:           string
+	initrd?:        string
+	initrdSecrets?: string
+	kernel:         string
+	kernelParams: [...string]
+	label:    string
+	toplevel: string
+	specialisation?: {
+		[=~"^"]: #V1
+	}
+	extensions?: {...}
+}
+
+Document: {
+	v1: #V1
+}
diff --git a/nixos/modules/system/activation/bootspec.nix b/nixos/modules/system/activation/bootspec.nix
new file mode 100644
index 000000000000..61407ab67558
--- /dev/null
+++ b/nixos/modules/system/activation/bootspec.nix
@@ -0,0 +1,126 @@
+# 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
+          {
+            v1 = {
+              system = config.boot.kernelPackages.stdenv.hostPlatform.system;
+              kernel = "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
+              kernelParams = config.boot.kernelParams;
+              label = "NixOS ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})";
+
+              inherit (cfg) extensions;
+            } // 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.jq}/bin/jq"
+            ''
+              .v1.toplevel = $toplevel |
+              .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.jq}/bin/jq"
+              "--sort-keys"
+              ".v1.specialisation = ($ARGS.named | map_values(. | first | .v1))"
+            ] + " ${lib.concatStringsSep " " specialisationLoader}";
+        in
+        ''
+          mkdir -p $out/bootspec
+
+          ${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 "Enable generation of RFC-0125 bootspec in $system/bootspec, e.g. /run/current-system/bootspec");
+
+    extensions = lib.mkOption {
+      type = lib.types.attrsOf lib.types.attrs; # <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;
+    };
+  };
+
+  config = lib.mkIf (cfg.enable) {
+    warnings = [
+      ''RFC-0125 is not merged yet, this is a feature preview of bootspec.
+        The schema is not definitive and features are not guaranteed to be stable until RFC-0125 is merged.
+        See:
+        - https://github.com/NixOS/nixpkgs/pull/172237 to track merge status in nixpkgs.
+        - https://github.com/NixOS/rfcs/pull/125 to track RFC status.
+      ''
+    ];
+  };
+}
diff --git a/nixos/modules/system/activation/specialisation.nix b/nixos/modules/system/activation/specialisation.nix
new file mode 100644
index 000000000000..86603c847641
--- /dev/null
+++ b/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/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 3f0d976e70b8..9a4c635402d1 100755
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -18,7 +18,8 @@ use Config::IniFiles;
 use File::Path qw(make_path);
 use File::Basename;
 use File::Slurp qw(read_file write_file edit_file);
-use Net::DBus;
+use JSON::PP;
+use IPC::Cmd;
 use Sys::Syslog qw(:standard :macros);
 use Cwd qw(abs_path);
 
@@ -124,12 +125,29 @@ EOF
 # virtual console 1 and we restart the "tty1" unit.
 $SIG{PIPE} = "IGNORE";
 
+# Replacement for Net::DBus that calls busctl of the current systemd, parses
+# it's json output and returns the response using only core modules to reduce
+# dependencies on perlPackages in baseSystem
+sub busctl_call_systemd1_mgr {
+    my (@args) = @_;
+    my $cmd = [
+        "$cur_systemd/busctl", "--json=short", "call", "org.freedesktop.systemd1",
+        "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager",
+        @args
+    ];
+
+    my ($ok, $err, undef, $stdout) = IPC::Cmd::run(command => $cmd);
+    die $err unless $ok;
+
+    my $res = decode_json(join "", @$stdout);
+    return $res;
+}
+
 # Asks the currently running systemd instance via dbus which units are active.
 # Returns a hash where the key is the name of each unit and the value a hash
 # of load, state, substate.
 sub get_active_units {
-    my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1");
-    my $units = $mgr->ListUnitsByPatterns([], []);
+    my $units = busctl_call_systemd1_mgr("ListUnitsByPatterns", "asas", 0, 0)->{data}->[0];
     my $res = {};
     for my $item (@{$units}) {
         my ($id, $description, $load_state, $active_state, $sub_state,
@@ -149,9 +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 $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1");
-    my $units = $mgr->ListUnitsByNames([$unit_name]);
+    my $units = busctl_call_systemd1_mgr("ListUnitsByNames", "as", 1, , "--", $unit_name)->{data}->[0];
     if (scalar(@{$units}) == 0) {
         return 0;
     }
diff --git a/nixos/modules/system/activation/test.nix b/nixos/modules/system/activation/test.nix
new file mode 100644
index 000000000000..8cf000451c6e
--- /dev/null
+++ b/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/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 84f560691fc4..00b11471e1c7 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/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,15 +59,10 @@ 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"
       substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
@@ -93,7 +75,14 @@ let
         fi
       ''}
 
-      echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies
+      ${config.system.systemBuilderCommands}
+
+      echo -n "$extraDependencies" > $out/extra-dependencies
+
+      ${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
+        ${config.boot.bootspec.writer}
+        ${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"
+      ''}
 
       ${config.system.extraSystemBuilderCmds}
     '';
@@ -103,7 +92,7 @@ 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;
@@ -121,11 +110,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 NetDBus ]);
-  };
+    perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
+  } // config.system.systemBuilderArgs);
 
   # Handle assertions and warnings
 
@@ -140,16 +129,6 @@ 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; } ];
-      };
-    };
-
 in
 
 {
@@ -161,53 +140,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 = "Include the entire system's configuration. Set to false to make a completely differently configured system.";
-          };
-
-          options.configuration = mkOption {
-            default = {};
-            description = ''
-              Arbitrary NixOS configuration.
-
-              Anything you can add to a normal NixOS configuration, you can add
-              here, including imports and config values, although nested
-              specialisations will be ignored.
-            '';
-            visible = "shallow";
-            inherit (extend { modules = [ ./no-clone.nix ]; }) type;
-          };
-        })
-      );
-    };
-
     system.boot.loader.id = mkOption {
       internal = true;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Id string of the used bootloader.
       '';
     };
@@ -217,7 +153,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 +162,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 = ''
@@ -255,7 +191,7 @@ in
       toplevel = mkOption {
         type = types.package;
         readOnly = true;
-        description = ''
+        description = lib.mdDoc ''
           This option contains the store path that typically represents a NixOS system.
 
           You can read this path in a custom deployment tool for example.
@@ -267,16 +203,16 @@ in
     system.copySystemConfiguration = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         If enabled, copies the NixOS configuration file
-        (usually <filename>/etc/nixos/configuration.nix</filename>)
+        (usually {file}`/etc/nixos/configuration.nix`)
         and links it from the resulting system
-        (getting to <filename>/run/current-system/configuration.nix</filename>).
+        (getting to {file}`/run/current-system/configuration.nix`).
         Note that only this single file is copied, even if it imports others.
       '';
     };
 
-    system.extraSystemBuilderCmds = mkOption {
+    system.systemBuilderCommands = mkOption {
       type = types.lines;
       internal = true;
       default = "";
@@ -285,10 +221,38 @@ 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 = ''
+      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.
@@ -302,12 +266,12 @@ in
         { ... }: {
           options.original = mkOption {
             type = types.package;
-            description = "The original package to override.";
+            description = lib.mdDoc "The original package to override.";
           };
 
           options.replacement = mkOption {
             type = types.package;
-            description = "The replacement package.";
+            description = lib.mdDoc "The replacement package.";
           };
         })
       );
@@ -315,7 +279,7 @@ in
         oldDependency = original;
         newDependency = replacement;
       });
-      description = ''
+      description = lib.mdDoc ''
         List of packages to override without doing a full rebuild.
         The original derivation and replacement derivation must have the same
         name length, and ideally should have close-to-identical directory layout.
@@ -333,11 +297,11 @@ in
         then "unnamed"
         else config.networking.hostName;
       '';
-      description = ''
-        The name of the system used in the <option>system.build.toplevel</option> derivation.
-        </para><para>
+      description = lib.mdDoc ''
+        The name of the system used in the {option}`system.build.toplevel` derivation.
+
         That derivation has the following name:
-        <literal>"nixos-system-''${config.system.name}-''${config.system.nixos.label}"</literal>
+        `"nixos-system-''${config.system.name}-''${config.system.nixos.label}"`
       '';
     };
 
@@ -351,12 +315,28 @@ in
         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.systemBuilderArgs = 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 = system;
 
   };
 
-  # uses extendModules to generate a type
-  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 33748358e45b..87e66f73be0e 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -153,7 +153,7 @@ in {
       registrations = mkOption {
         default = {};
 
-        description = ''
+        description = lib.mdDoc ''
           Extra binary formats to register with the kernel.
           See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
         '';
@@ -162,30 +162,30 @@ in {
           options = {
             recognitionType = mkOption {
               default = "magic";
-              description = "Whether to recognize executables by magic number or extension.";
+              description = lib.mdDoc "Whether to recognize executables by magic number or extension.";
               type = types.enum [ "magic" "extension" ];
             };
 
             offset = mkOption {
               default = null;
-              description = "The byte offset of the magic number used for recognition.";
+              description = lib.mdDoc "The byte offset of the magic number used for recognition.";
               type = types.nullOr types.int;
             };
 
             magicOrExtension = mkOption {
-              description = "The magic number or extension to match on.";
+              description = lib.mdDoc "The magic number or extension to match on.";
               type = types.str;
             };
 
             mask = mkOption {
               default = null;
               description =
-                "A mask to be ANDed with the byte sequence of the file before matching";
+                lib.mdDoc "A mask to be ANDed with the byte sequence of the file before matching";
               type = types.nullOr types.str;
             };
 
             interpreter = mkOption {
-              description = ''
+              description = lib.mdDoc ''
                 The interpreter to invoke to run the program.
 
                 Note that the actual registration will point to
@@ -197,7 +197,7 @@ in {
 
             preserveArgvZero = mkOption {
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to pass the original argv[0] to the interpreter.
 
                 See the description of the 'P' flag in the kernel docs
@@ -208,7 +208,7 @@ in {
 
             openBinary = mkOption {
               default = config.matchCredentials;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to pass the binary to the interpreter as an open
                 file descriptor, instead of a path.
               '';
@@ -217,7 +217,7 @@ in {
 
             matchCredentials = mkOption {
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to launch with the credentials and security
                 token of the binary, not the interpreter (e.g. setuid
                 bit).
@@ -232,7 +232,7 @@ in {
 
             fixBinary = mkOption {
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to open the interpreter file as soon as the
                 registration is loaded, rather than waiting for a
                 relevant file to be invoked.
@@ -245,7 +245,7 @@ in {
 
             wrapInterpreterInShell = mkOption {
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to wrap the interpreter in a shell script.
 
                 This allows a shell command to be set as the interpreter.
@@ -256,7 +256,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;
@@ -268,7 +268,7 @@ in {
       emulatedSystems = mkOption {
         default = [];
         example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ];
-        description = ''
+        description = lib.mdDoc ''
           List of systems to emulate. Will also configure Nix to
           support your new systems.
           Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
@@ -321,5 +321,6 @@ in {
       "proc-sys-fs-binfmt_misc.mount"
       "systemd-binfmt.service"
     ];
+    systemd.services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
   };
 }
diff --git a/nixos/modules/system/boot/emergency-mode.nix b/nixos/modules/system/boot/emergency-mode.nix
index ec697bcee268..a2163aa5ffb3 100644
--- a/nixos/modules/system/boot/emergency-mode.nix
+++ b/nixos/modules/system/boot/emergency-mode.nix
@@ -11,9 +11,9 @@ with lib;
     systemd.enableEmergencyMode = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable emergency mode, which is an
-        <command>sulogin</command> shell started on the console if
+        {command}`sulogin` shell started on the console if
         mounting a filesystem fails.  Since some machines (like EC2
         instances) have no console of any kind, emergency mode doesn't
         make sense, and it's better to continue with the boot insofar
diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix
index 87c981b24cec..034b2b9906f5 100644
--- a/nixos/modules/system/boot/grow-partition.nix
+++ b/nixos/modules/system/boot/grow-partition.nix
@@ -12,7 +12,7 @@ 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 {
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index 2a7417ed3715..a1017c3e2420 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -50,18 +50,17 @@ in
     boot.initrd.network.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Add network connectivity support to initrd. The network may be
-        configured using the <literal>ip</literal> kernel parameter,
-        as described in <link
-        xlink:href="https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt">the
-        kernel documentation</link>.  Otherwise, if
-        <option>networking.useDHCP</option> is enabled, an IP address
+        configured using the `ip` kernel parameter,
+        as described in [the kernel documentation](https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt).
+        Otherwise, if
+        {option}`networking.useDHCP` is enabled, an IP address
         is acquired using DHCP.
 
         You should add the module(s) required for your network card to
         boot.initrd.availableKernelModules.
-        <literal>lspci -v | grep -iA8 'network\|ethernet'</literal>
+        `lspci -v | grep -iA8 'network\|ethernet'`
         will tell you which.
       '';
     };
@@ -69,7 +68,7 @@ in
     boot.initrd.network.flushBeforeStage2 = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      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.
@@ -79,9 +78,9 @@ in
     boot.initrd.network.udhcpc.extraArgs = mkOption {
       default = [];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         Additional command-line arguments passed verbatim to udhcpc if
-        <option>boot.initrd.network.enable</option> and <option>networking.useDHCP</option>
+        {option}`boot.initrd.network.enable` and {option}`networking.useDHCP`
         are enabled.
       '';
     };
@@ -89,7 +88,7 @@ in
     boot.initrd.network.postCommands = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed after stage 1 of the
         boot has initialised the network.
       '';
diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix
index 9b52d4bbdb1e..b41e7524320e 100644
--- a/nixos/modules/system/boot/initrd-openvpn.nix
+++ b/nixos/modules/system/boot/initrd-openvpn.nix
@@ -15,25 +15,23 @@ in
     boot.initrd.network.openvpn.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Starts an OpenVPN client during initrd boot. It can be used to e.g.
         remotely accessing the SSH service controlled by
-        <option>boot.initrd.network.ssh</option> or other network services
+        {option}`boot.initrd.network.ssh` or other network services
         included. Service is killed when stage-1 boot is finished.
       '';
     };
 
     boot.initrd.network.openvpn.configuration = mkOption {
       type = types.path; # Same type as boot.initrd.secrets
-      description = ''
+      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";
     };
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 0999142de86e..701d242abc15 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -14,20 +14,20 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Start SSH service during initrd boot. It can be used to debug failing
         boot on a remote server, enter pasphrase for an encrypted partition etc.
         Service is killed when stage-1 boot is finished.
 
         The sshd configuration is largely inherited from
-        <option>services.openssh</option>.
+        {option}`services.openssh`.
       '';
     };
 
     port = mkOption {
-      type = types.int;
+      type = types.port;
       default = 22;
-      description = ''
+      description = lib.mdDoc ''
         Port on which SSH initrd service should listen.
       '';
     };
@@ -35,7 +35,7 @@ in
     shell = mkOption {
       type = types.str;
       default = "/bin/ash";
-      description = ''
+      description = lib.mdDoc ''
         Login shell of the remote user. Can be used to limit actions user can do.
       '';
     };
@@ -47,31 +47,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.
       '';
     };
 
@@ -79,7 +86,7 @@ in
       type = types.listOf types.str;
       default = config.users.users.root.openssh.authorizedKeys.keys;
       defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys";
-      description = ''
+      description = lib.mdDoc ''
         Authorized keys for the root user on initrd.
       '';
     };
@@ -87,7 +94,7 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = "Verbatim contents of <filename>sshd_config</filename>.";
+      description = lib.mdDoc "Verbatim contents of {file}`sshd_config`.";
     };
   };
 
@@ -143,7 +150,7 @@ 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
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index fad00e39497d..b13e50cb17d2 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/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,17 +51,22 @@ 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).
       '';
     };
 
@@ -66,16 +74,16 @@ in
       type = types.listOf types.attrs;
       default = [];
       example = literalExpression "[ pkgs.kernelPatches.ubuntu_fan_4_4 ]";
-      description = "A list of additional patches to apply to the kernel.";
+      description = lib.mdDoc "A list of additional patches to apply to the kernel.";
     };
 
     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.
@@ -88,14 +96,14 @@ in
         description = "string, with spaces inside double quotes";
       });
       default = [ ];
-      description = "Parameters added to the kernel command line.";
+      description = lib.mdDoc "Parameters added to the kernel command line.";
     };
 
     boot.consoleLogLevel = mkOption {
       type = types.int;
       default = 4;
-      description = ''
-        The kernel console <literal>loglevel</literal>. All Kernel Messages with a log level smaller
+      description = lib.mdDoc ''
+        The kernel console `loglevel`. All Kernel Messages with a log level smaller
         than this setting will be printed to the console.
       '';
     };
@@ -103,11 +111,11 @@ in
     boot.vesa = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         (Deprecated) This option, if set, activates the VESA 800x600 video
         mode on boot and disables kernel modesetting. It is equivalent to
-        specifying <literal>[ "vga=0x317" "nomodeset" ]</literal> in the
-        <option>boot.kernelParams</option> option. This option is
+        specifying `[ "vga=0x317" "nomodeset" ]` in the
+        {option}`boot.kernelParams` option. This option is
         deprecated as of 2020: Xorg now works better with modesetting, and
         you might want a different VESA vga setting, anyway.
       '';
@@ -117,18 +125,18 @@ in
       type = types.listOf types.package;
       default = [];
       example = literalExpression "[ config.boot.kernelPackages.nvidia_x11 ]";
-      description = "A list of additional packages supplying kernel modules.";
+      description = lib.mdDoc "A list of additional packages supplying kernel modules.";
     };
 
     boot.kernelModules = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = ''
+      description = lib.mdDoc ''
         The set of kernel modules to be loaded in the second stage of
         the boot process.  Note that modules that are needed to
         mount the root file system should be added to
-        <option>boot.initrd.availableKernelModules</option> or
-        <option>boot.initrd.kernelModules</option>.
+        {option}`boot.initrd.availableKernelModules` or
+        {option}`boot.initrd.kernelModules`.
       '';
     };
 
@@ -136,7 +144,7 @@ in
       type = types.listOf types.str;
       default = [];
       example = [ "sata_nv" "ext3" ];
-      description = ''
+      description = lib.mdDoc ''
         The set of kernel modules in the initial ramdisk used during the
         boot process.  This set must include all modules necessary for
         mounting the root device.  That is, it should include modules
@@ -149,23 +157,23 @@ in
         loaded automatically when an ext3 filesystem is mounted, and
         modules for PCI devices are loaded when they match the PCI ID
         of a device in your system).  To force a module to be loaded,
-        include it in <option>boot.initrd.kernelModules</option>.
+        include it in {option}`boot.initrd.kernelModules`.
       '';
     };
 
     boot.initrd.kernelModules = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = "List of modules that are always loaded by the initrd.";
+      description = lib.mdDoc "List of modules that are always loaded by the initrd.";
     };
 
     boot.initrd.includeDefaultModules = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         This option, if set, adds a collection of default kernel modules
-        to <option>boot.initrd.availableKernelModules</option> and
-        <option>boot.initrd.kernelModules</option>.
+        to {option}`boot.initrd.availableKernelModules` and
+        {option}`boot.initrd.kernelModules`.
       '';
     };
 
@@ -173,7 +181,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 +201,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 +266,7 @@ in
           ];
       })
 
-      (mkIf (!config.boot.isContainer) {
+      (mkIf config.boot.kernel.enable {
         system.build = { inherit kernel; };
 
         system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages;
@@ -273,9 +281,6 @@ in
 
         boot.kernelModules = [ "loop" "atkbd" ];
 
-        # The Linux kernel >= 2.6.27 provides firmware.
-        hardware.firmware = [ kernel ];
-
         # Create /etc/modules-load.d/nixos.conf, which is read by
         # systemd-modules-load.service to load required kernel modules.
         environment.etc =
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
index 495fe74bc21e..31e9ec626ca6 100644
--- a/nixos/modules/system/boot/kernel_config.nix
+++ b/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".
         '';
       };
@@ -25,7 +25,7 @@ let
         };
         default = null;
         example = ''MMC_BLOCK_MINORS.freeform = "32";'';
-        description = ''
+        description = lib.mdDoc ''
           Freeform description of a kernel configuration item value.
         '';
       };
@@ -33,7 +33,7 @@ let
       optional = mkOption {
         type = types.bool // { merge = mergeFalseByDefault; };
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether option should generate a failure when unused.
           Upon merging values, mandatory wins over optional.
         '';
@@ -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`.
@@ -105,7 +105,7 @@ in
         USB = option yes;
         MMC_BLOCK_MINORS = freeform "32";
       }'';
-      description = ''
+      description = lib.mdDoc ''
         Structured kernel configuration.
       '';
     };
diff --git a/nixos/modules/system/boot/loader/efi.nix b/nixos/modules/system/boot/loader/efi.nix
index 6043c904c450..2661f362249d 100644
--- a/nixos/modules/system/boot/loader/efi.nix
+++ b/nixos/modules/system/boot/loader/efi.nix
@@ -8,13 +8,13 @@ with lib;
     canTouchEfiVariables = mkOption {
       default = false;
       type = types.bool;
-      description = "Whether the installation process is allowed to modify EFI boot variables.";
+      description = lib.mdDoc "Whether the installation process is allowed to modify EFI boot variables.";
     };
 
     efiSysMountPoint = mkOption {
       default = "/boot";
       type = types.str;
-      description = "Where the EFI System Partition is mounted.";
+      description = lib.mdDoc "Where the EFI System Partition is mounted.";
     };
   };
 }
diff --git a/nixos/modules/system/boot/loader/external/external.md b/nixos/modules/system/boot/loader/external/external.md
new file mode 100644
index 000000000000..ba1dfd4d9b9a
--- /dev/null
+++ b/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
+
+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/nixos/modules/system/boot/loader/external/external.nix b/nixos/modules/system/boot/loader/external/external.nix
new file mode 100644
index 000000000000..5cf478e6c83c
--- /dev/null
+++ b/nixos/modules/system/boot/loader/external/external.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.boot.loader.external;
+in
+{
+  meta = {
+    maintainers = with maintainers; [ cole-h grahamc raitobezarius ];
+    # Don't edit the docbook xml directly, edit the md and generate it:
+    # `pandoc external.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > external.xml`
+    doc = ./external.xml;
+  };
+
+  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/nixos/modules/system/boot/loader/external/external.xml b/nixos/modules/system/boot/loader/external/external.xml
new file mode 100644
index 000000000000..39ab2156bc8c
--- /dev/null
+++ b/nixos/modules/system/boot/loader/external/external.xml
@@ -0,0 +1,41 @@
+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-bootloader-external">
+  <title>External Bootloader Backends</title>
+  <para>
+    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.
+  </para>
+  <para>
+    You can replace the built-in bootloader support with your own
+    tooling using the <quote>external</quote> bootloader option.
+  </para>
+  <para>
+    Imagine you have created a new package called FooBoot. FooBoot
+    provides a program at
+    <literal>${pkgs.fooboot}/bin/fooboot-install</literal> which takes
+    the system closure’s path as its only argument and configures the
+    system’s bootloader.
+  </para>
+  <para>
+    You can enable FooBoot like this:
+  </para>
+  <programlisting language="nix">
+{ pkgs, ... }: {
+  boot.loader.external = {
+    enable = true;
+    installHook = &quot;${pkgs.fooboot}/bin/fooboot-install&quot;;
+  };
+}
+</programlisting>
+  <section xml:id="developing-custom-bootloader-backends">
+    <title>Developing Custom Bootloader Backends</title>
+    <para>
+      Bootloaders should use
+      <link xlink:href="https://github.com/NixOS/rfcs/pull/125">RFC-0125</link>’s
+      Bootspec format and synthesis tools to identify the key properties
+      for bootable system generations.
+    </para>
+  </section>
+</chapter>
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
index 1437ab387700..5ace5dd06fd4 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -22,11 +22,11 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create symlinks to the system generations under
-          <literal>/boot</literal>.  When enabled,
-          <literal>/boot/default/kernel</literal>,
-          <literal>/boot/default/initrd</literal>, etc., are updated to
+          `/boot`.  When enabled,
+          `/boot/default/kernel`,
+          `/boot/default/initrd`, etc., are updated to
           point to the current generation's kernel image, initial RAM
           disk, and other bootstrap files.
 
@@ -41,7 +41,7 @@ in
       copyKernels = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether copy the necessary boot files into /boot, so
           /nix/store is not needed by the boot loader.
         '';
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index 545b594674f3..5ef3c5cd52a8 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -20,12 +20,12 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to generate an extlinux-compatible configuration file
-          under <literal>/boot/extlinux.conf</literal>.  For instance,
+          under `/boot/extlinux.conf`.  For instance,
           U-Boot's generic distro boot support uses this file format.
 
-          See <link xlink:href="http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master">U-boot's documentation</link>
+          See [U-boot's documentation](http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master)
           for more information.
         '';
       };
@@ -33,7 +33,7 @@ in
       useGenerationDeviceTree = mkOption {
         default = true;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to generate Device Tree-related directives in the
           extlinux configuration.
 
@@ -49,7 +49,7 @@ in
         default = 20;
         example = 10;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Maximum number of configurations in the boot menu.
         '';
       };
@@ -57,9 +57,9 @@ in
       populateCmd = mkOption {
         type = types.str;
         readOnly = true;
-        description = ''
+        description = lib.mdDoc ''
           Contains the builder command used to populate an image,
-          honoring all options except the <literal>-c &lt;path-to-default-configuration&gt;</literal>
+          honoring all options except the `-c <path-to-default-configuration>`
           argument.
           Useful to have for sdImage.populateRootCommands
         '';
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 1f915d1f419c..1d266b5a37d5 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -38,7 +38,7 @@ let
   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;
@@ -103,7 +103,7 @@ in
         default = !config.boot.isContainer;
         defaultText = literalExpression "!config.boot.isContainer";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to enable the GNU GRUB boot loader.
         '';
       };
@@ -112,9 +112,9 @@ in
         default = 2;
         example = 1;
         type = types.int;
-        description = ''
-          The version of GRUB to use: <literal>1</literal> for GRUB
-          Legacy (versions 0.9x), or <literal>2</literal> (the
+        description = lib.mdDoc ''
+          The version of GRUB to use: `1` for GRUB
+          Legacy (versions 0.9x), or `2` (the
           default) for GRUB 2.
         '';
       };
@@ -123,12 +123,12 @@ in
         default = "";
         example = "/dev/disk/by-id/wwn-0x500001234567890a";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The device on which the GRUB boot loader will be installed.
-          The special value <literal>nodev</literal> means that a GRUB
+          The special value `nodev` means that a GRUB
           boot menu will be generated, but GRUB itself will not
           actually be installed.  To install GRUB on multiple devices,
-          use <literal>boot.loader.grub.devices</literal>.
+          use `boot.loader.grub.devices`.
         '';
       };
 
@@ -136,9 +136,9 @@ in
         default = [];
         example = [ "/dev/disk/by-id/wwn-0x500001234567890a" ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The devices on which the boot loader, GRUB, will be
-          installed. Can be used instead of <literal>device</literal> to
+          installed. Can be used instead of `device` to
           install GRUB onto multiple devices.
         '';
       };
@@ -148,7 +148,7 @@ in
         example = {
           root = { hashedPasswordFile = "/path/to/file"; };
         };
-        description = ''
+        description = lib.mdDoc ''
           User accounts for GRUB. When specified, the GRUB command line and
           all boot options except the default are password-protected.
           All passwords and hashes provided will be stored in /boot/grub/grub.cfg,
@@ -163,7 +163,7 @@ in
               example = "/path/to/file";
               default = null;
               type = with types; uniq (nullOr str);
-              description = ''
+              description = lib.mdDoc ''
                 Specifies the path to a file containing the password hash
                 for the account, generated with grub-mkpasswd-pbkdf2.
                 This hash will be stored in /boot/grub/grub.cfg, and will
@@ -174,7 +174,7 @@ in
               example = "grub.pbkdf2.sha512.10000.674DFFDEF76E13EA...2CC972B102CF4355";
               default = null;
               type = with types; uniq (nullOr str);
-              description = ''
+              description = lib.mdDoc ''
                 Specifies the password hash for the account,
                 generated with grub-mkpasswd-pbkdf2.
                 This hash will be copied to the Nix store, and will be visible to all local users.
@@ -184,7 +184,7 @@ in
               example = "/path/to/file";
               default = null;
               type = with types; uniq (nullOr str);
-              description = ''
+              description = lib.mdDoc ''
                 Specifies the path to a file containing the
                 clear text password for the account.
                 This password will be stored in /boot/grub/grub.cfg, and will
@@ -195,7 +195,7 @@ in
               example = "Pa$$w0rd!";
               default = null;
               type = with types; uniq (nullOr str);
-              description = ''
+              description = lib.mdDoc ''
                 Specifies the clear text password for the account.
                 This password will be copied to the Nix store, and will be visible to all local users.
               '';
@@ -210,7 +210,7 @@ in
           { path = "/boot1"; devices = [ "/dev/disk/by-id/wwn-0x500001234567890a" ]; }
           { path = "/boot2"; devices = [ "/dev/disk/by-id/wwn-0x500009876543210a" ]; }
         ];
-        description = ''
+        description = lib.mdDoc ''
           Mirror the boot configuration to multiple partitions and install grub
           to the respective devices corresponding to those partitions.
         '';
@@ -221,7 +221,7 @@ in
             path = mkOption {
               example = "/boot1";
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The path to the boot directory where GRUB will be written. Generally
                 this boot path should double as an EFI path.
               '';
@@ -231,7 +231,7 @@ in
               default = null;
               example = "/boot1/efi";
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The path to the efi system mount point. Usually this is the same
                 partition as the above path and can be left as null.
               '';
@@ -241,10 +241,10 @@ in
               default = null;
               example = "NixOS-fsid";
               type = types.nullOr types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The id of the bootloader to store in efi nvram.
                 The default is to name it NixOS and append the path or efiSysMountPoint.
-                This is only used if <literal>boot.loader.efi.canTouchEfiVariables</literal> is true.
+                This is only used if `boot.loader.efi.canTouchEfiVariables` is true.
               '';
             };
 
@@ -252,7 +252,7 @@ in
               default = [ ];
               example = [ "/dev/disk/by-id/wwn-0x500001234567890a" "/dev/disk/by-id/wwn-0x500009876543210a" ];
               type = types.listOf types.str;
-              description = ''
+              description = lib.mdDoc ''
                 The path to the devices which will have the GRUB MBR written.
                 Note these are typically device paths and not paths to partitions.
               '';
@@ -266,7 +266,7 @@ in
         default = "";
         example = "Stable 2.6.21";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           GRUB entry name instead of default.
         '';
       };
@@ -274,7 +274,7 @@ in
       storePath = mkOption {
         default = "/nix/store";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Path to the Nix store when looking for kernels at boot.
           Only makes sense when copyKernels is false.
         '';
@@ -283,7 +283,7 @@ in
       extraPrepareConfig = mkOption {
         default = "";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Additional bash commands to be run at the script that
           prepares the GRUB menu entries.
         '';
@@ -297,7 +297,7 @@ in
           terminal_output --append serial
         '';
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Additional GRUB commands inserted in the configuration file
           just before the menu entries.
         '';
@@ -307,26 +307,26 @@ in
         default = [ ];
         example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ];
         type = types.listOf types.str;
-        description = ''
-          Additional arguments passed to <literal>grub-install</literal>.
+        description = lib.mdDoc ''
+          Additional arguments passed to `grub-install`.
 
           A use case for this is to build specific GRUB2 modules
           directly into the GRUB2 kernel image, so that they are available
-          and activated even in the <literal>grub rescue</literal> shell.
+          and activated even in the `grub rescue` shell.
 
           They are also necessary when the BIOS/UEFI is bugged and cannot
           correctly read large disks (e.g. above 2 TB), so GRUB2's own
-          <literal>nativedisk</literal> and related modules can be used
+          `nativedisk` and related modules can be used
           to use its own disk drivers. The example shows one such case.
           This is also useful for booting from USB.
           See the
-          <link xlink:href="http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326">
+          [
           GRUB source code
-          </link>
+          ](http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326)
           for which disk modules are available.
 
-          The list elements are passed directly as <literal>argv</literal>
-          arguments to the <literal>grub-install</literal> program, in order.
+          The list elements are passed directly as `argv`
+          arguments to the `grub-install` program, in order.
         '';
       };
 
@@ -344,7 +344,7 @@ in
           export GNUPGHOME=$old_gpg_home
         '';
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Additional shell commands inserted in the bootloader installer
           script after generating menu entries.
         '';
@@ -354,7 +354,7 @@ in
         default = "";
         example = "root (hd0)";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Additional GRUB commands inserted in the configuration file
           at the start of each NixOS menu entry.
         '';
@@ -379,7 +379,7 @@ in
             chainloader /efi/fedora/grubx64.efi
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           Any additional entries you want added to the GRUB boot menu.
         '';
       };
@@ -387,7 +387,7 @@ in
       extraEntriesBeforeNixOS = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether extraEntries are included before the default option.
         '';
       };
@@ -398,10 +398,10 @@ in
         example = literalExpression ''
           { "memtest.bin" = "''${pkgs.memtest86plus}/memtest.bin"; }
         '';
-        description = ''
-          A set of files to be copied to <filename>/boot</filename>.
+        description = lib.mdDoc ''
+          A set of files to be copied to {file}`/boot`.
           Each attribute name denotes the destination file name in
-          <filename>/boot</filename>, while the corresponding
+          {file}`/boot`, while the corresponding
           attribute value specifies the source file.
         '';
       };
@@ -409,7 +409,7 @@ in
       useOSProber = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set to true, append entries for other OSs detected by os-prober.
         '';
       };
@@ -417,23 +417,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 +441,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 +478,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>
+          :::
         '';
       };
 
@@ -503,7 +503,7 @@ in
         type = types.nullOr types.path;
         default = "${realGrub}/share/grub/unicode.pf2";
         defaultText = literalExpression ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
-        description = ''
+        description = lib.mdDoc ''
           Path to a TrueType, OpenType, or pf2 font to be used by Grub.
         '';
       };
@@ -512,8 +512,8 @@ in
         type = types.nullOr types.int;
         example = 16;
         default = null;
-        description = ''
-          Font size for the grub menu. Ignored unless <literal>font</literal>
+        description = lib.mdDoc ''
+          Font size for the grub menu. Ignored unless `font`
           is set to a ttf or otf font.
         '';
       };
@@ -522,7 +522,7 @@ in
         default = "auto";
         example = "1024x768";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The gfxmode to pass to GRUB when loading a graphical boot interface under EFI.
         '';
       };
@@ -531,7 +531,7 @@ in
         default = "1024x768";
         example = "auto";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The gfxmode to pass to GRUB when loading a graphical boot interface under BIOS.
         '';
       };
@@ -540,7 +540,7 @@ in
         default = "keep";
         example = "text";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI.
         '';
       };
@@ -549,7 +549,7 @@ in
         default = "text";
         example = "keep";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The gfxpayload to pass to GRUB when loading a graphical boot interface under BIOS.
         '';
       };
@@ -558,7 +558,7 @@ in
         default = 100;
         example = 120;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Maximum of configurations in boot menu. GRUB has problems when
           there are too many entries.
         '';
@@ -567,7 +567,7 @@ in
       copyKernels = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether the GRUB menu builder should copy kernels and initial
           ramdisks to /boot.  This is done automatically if /boot is
           on a different partition than /.
@@ -578,7 +578,7 @@ in
         default = "0";
         type = types.either types.int types.str;
         apply = toString;
-        description = ''
+        description = lib.mdDoc ''
           Index of the default menu item to be booted.
           Can also be set to "saved", which will make GRUB select
           the menu item that was used at the last boot.
@@ -588,13 +588,13 @@ in
       fsIdentifier = mkOption {
         default = "uuid";
         type = types.enum [ "uuid" "label" "provided" ];
-        description = ''
+        description = lib.mdDoc ''
           Determines how GRUB will identify devices when generating the
           configuration file. A value of uuid / label signifies that grub
           will always resolve the uuid or label of the device before using
           it in the configuration. A value of provided means that GRUB will
-          use the device name as show in <command>df</command> or
-          <command>mount</command>. Note, zfs zpools / datasets are ignored
+          use the device name as show in {command}`df` or
+          {command}`mount`. Note, zfs zpools / datasets are ignored
           and will always be mounted using their labels.
         '';
       };
@@ -602,7 +602,7 @@ in
       zfsSupport = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether GRUB should be built against libzfs.
           ZFS support is only available for GRUB v2.
           This option is ignored for GRUB v1.
@@ -612,7 +612,7 @@ in
       efiSupport = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether GRUB should be built with EFI support.
           EFI support is only available for GRUB v2.
           This option is ignored for GRUB v1.
@@ -622,44 +622,42 @@ in
       efiInstallAsRemovable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Whether to invoke <literal>grub-install</literal> with
-          <literal>--removable</literal>.</para>
+        description = lib.mdDoc ''
+          Whether to invoke `grub-install` with
+          `--removable`.
 
-          <para>Unless you turn this on, GRUB will install itself somewhere in
-          <literal>boot.loader.efi.efiSysMountPoint</literal> (exactly where
+          Unless you turn this on, GRUB will install itself somewhere in
+          `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>").</para>
+          the warning that gets printed ("`efibootmgr: EFI variables
+          are not supported on this system.`").
 
-          <para>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
-          are hardcoded to try first, regardless of NVRAM EFI variables.</para>
+          If you turn this feature on, GRUB will install itself in a
+          special location within `efiSysMountPoint` (namely
+          `EFI/boot/boot$arch.efi`) which the firmwares
+          are hardcoded to try first, regardless of NVRAM EFI variables.
 
-          <para>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>
+          To summarize, turn this on if:
+          - 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
         '';
       };
 
       enableCryptodisk = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable support for encrypted partitions. GRUB should automatically
           unlock the correct encrypted partition and look for filesystems.
         '';
@@ -668,7 +666,7 @@ in
       forceInstall = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to try and forcibly install GRUB even if problems are
           detected. It is not recommended to enable this unless you know what
           you are doing.
@@ -678,7 +676,7 @@ in
       forcei686 = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to force the use of a ia32 boot loader on x64 systems. Required
           to install and run NixOS on 64bit x86 systems with 32bit (U)EFI.
         '';
@@ -689,7 +687,7 @@ in
         enable = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Enable trusted boot. GRUB will measure all critical components during
             the boot process to offer TCG (TPM) support.
           '';
@@ -699,7 +697,7 @@ in
           default = "";
           example = "YES_TPM_is_activated";
           type = types.str;
-          description = ''
+          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.
@@ -709,7 +707,7 @@ in
         isHPLaptop = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Use a special version of TrustedGRUB that is needed by some HP laptops
             and works only for the HP laptops.
           '';
@@ -750,6 +748,11 @@ 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 {
diff --git a/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixos/modules/system/boot/loader/grub/ipxe.nix
index ef8595592f41..adddcbee0164 100644
--- a/nixos/modules/system/boot/loader/grub/ipxe.nix
+++ b/nixos/modules/system/boot/loader/grub/ipxe.nix
@@ -28,7 +28,7 @@ in
     { boot.loader.grub.ipxe = mkOption {
         type = types.attrsOf (types.either types.path types.str);
         description =
-          ''
+          lib.mdDoc ''
             Set of iPXE scripts available for
             booting from the GRUB boot menu.
           '';
diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix
index 71e50dd0577e..ccb6e8cc3caf 100644
--- a/nixos/modules/system/boot/loader/grub/memtest.nix
+++ b/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -18,12 +18,12 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Make Memtest86+ (or MemTest86 if EFI support is enabled),
           a memory testing program, available from the
           GRUB boot menu. MemTest86 is an unfree program, so
-          this requires <literal>allowUnfree</literal> to be set to
-          <literal>true</literal>.
+          this requires `allowUnfree` to be set to
+          `true`.
         '';
       };
 
@@ -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>
-
-          <listitem>
-            <para><literal>maxcpus=N</literal>, limit number of CPUs.</para>
-          </listitem>
-
-          <listitem>
-            <para><literal>onepass</literal>, run one pass and exit if there
-            are no errors.</para>
-          </listitem>
-
-          <listitem>
-            <para><literal>tstlist=...</literal>, list of tests to run.
-            Example: <literal>0,1,2</literal>.</para>
-          </listitem>
-
-          <listitem>
-            <para><literal>cpumask=...</literal>, set a CPU mask, to select CPUs
-            to use for testing.</para>
-          </listitem>
-
-          </itemizedlist>
+            `console=ttyS0`,
+            `console=ttyS0,9600` or
+            `console=ttyS0,115200n8`.
+
+          - `btrace`, enable boot trace.
+
+          - `maxcpus=N`, limit number of CPUs.
+
+          - `onepass`, run one pass and exit if there
+            are no errors.
+
+          - `tstlist=...`, list of tests to run.
+            Example: `0,1,2`.
+
+          - `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.
diff --git a/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixos/modules/system/boot/loader/init-script/init-script.nix
index 374d9524ff1e..8287131d3213 100644
--- a/nixos/modules/system/boot/loader/init-script/init-script.nix
+++ b/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -24,7 +24,7 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Some systems require a /sbin/init script which is started.
           Or having it makes starting NixOS easier.
           This applies to some kind of hosting services and user mode linux.
diff --git a/nixos/modules/system/boot/loader/loader.nix b/nixos/modules/system/boot/loader/loader.nix
index 01475f79b9c2..0e33264271bf 100644
--- a/nixos/modules/system/boot/loader/loader.nix
+++ b/nixos/modules/system/boot/loader/loader.nix
@@ -12,7 +12,7 @@ with lib;
         boot.loader.timeout =  mkOption {
             default = 5;
             type = types.nullOr types.int;
-            description = ''
+            description = lib.mdDoc ''
               Timeout (in seconds) until loader boots the default menu item. Use null if the loader menu should be displayed indefinitely.
             '';
         };
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
index 1023361f0b1f..1dde55074336 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -48,24 +48,24 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether to create files with the system generations in
-          <literal>/boot</literal>.
-          <literal>/boot/old</literal> will hold files from old generations.
+          `/boot`.
+          `/boot/old` will hold files from old generations.
         '';
       };
 
       version = mkOption {
         default = 2;
         type = types.enum [ 0 1 2 3 4 ];
-        description = "";
+        description = lib.mdDoc "";
       };
 
       uboot = {
         enable = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             Enable using uboot as bootmanager for the raspberry pi.
           '';
         };
@@ -74,7 +74,7 @@ in
           default = 20;
           example = 10;
           type = types.int;
-          description = ''
+          description = lib.mdDoc ''
             Maximum number of configurations in the boot menu.
           '';
         };
@@ -84,9 +84,9 @@ in
       firmwareConfig = mkOption {
         default = null;
         type = types.nullOr types.lines;
-        description = ''
-          Extra options that will be appended to <literal>/boot/config.txt</literal> file.
-          For possible values, see: https://www.raspberrypi.org/documentation/configuration/config-txt/
+        description = lib.mdDoc ''
+          Extra options that will be appended to `/boot/config.txt` file.
+          For possible values, see: https://www.raspberrypi.com/documentation/computers/config_txt.html
         '';
       };
     };
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index fa879437fd81..68da20615917 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -175,22 +175,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)
@@ -204,7 +204,6 @@ def get_profiles() -> List[str]:
     else:
         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')
@@ -241,30 +240,30 @@ def main() -> None:
         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@"] + flags + ["install"])
     else:
         # Update bootloader to latest if needed
-        systemd_version = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2]
-        sdboot_status = subprocess.check_output(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "status"], universal_newlines=True)
+        available_out = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2]
+        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
-        m = re.search("^\W+File:.*/EFI/(BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
-                      sdboot_status, re.IGNORECASE | re.MULTILINE)
+        installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
+                      installed_out, re.IGNORECASE | re.MULTILINE)
 
-        needs_install = False
+        available_match = re.search(r"^\((.*)\)$", available_out)
 
-        if m is None:
-            print("could not find any previously installed systemd-boot, installing.")
-            # Let systemd-boot attempt an installation if a previous one wasn't found
-            needs_install = True
-        else:
-            sdboot_version = f'({m.group(2)})'
-            if systemd_version != sdboot_version:
-                print("updating systemd-boot from %s to %s" % (sdboot_version, systemd_version))
-                needs_install = True
+        if installed_match is None:
+            raise Exception("could not find any previously installed systemd-boot")
+
+        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)
 
-        if needs_install:
-            subprocess.check_call(["@systemd@/bin/bootctl", "--path=@efiSysMountPoint@", "update"])
+        if installed_version < available_version:
+            print("updating systemd-boot from %s to %s" % (installed_version, available_version))
+            subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@", "update"])
 
     mkdir_p("@efiSysMountPoint@/efi/nixos")
     mkdir_p("@efiSysMountPoint@/loader/entries")
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index c07567ec82ea..8cb7c7b8e47b 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -33,7 +33,7 @@ let
     netbootxyz = if cfg.netbootxyz.enable then pkgs.netbootxyz-efi else "";
 
     copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
-      empty_file=$(mktemp)
+      empty_file=$(${pkgs.coreutils}/bin/mktemp)
 
       ${concatStrings (mapAttrsToList (n: v: ''
         ${pkgs.coreutils}/bin/install -Dp "${v}" "${efi.efiSysMountPoint}/"${escapeShellArg n}
@@ -57,6 +57,12 @@ let
       --disallow-untyped-defs \
       $out
   '';
+
+  finalSystemdBootBuilder = pkgs.writeScript "install-systemd-boot.sh" ''
+    #!${pkgs.runtimeShell}
+    ${checkedSystemdBootBuilder} "$@"
+    ${cfg.extraInstallCommands}
+  '';
 in {
 
   imports =
@@ -69,7 +75,7 @@ in {
 
       type = types.bool;
 
-      description = "Whether to enable the systemd-boot (formerly gummiboot) EFI boot manager";
+      description = lib.mdDoc "Whether to enable the systemd-boot (formerly gummiboot) EFI boot manager";
     };
 
     editor = mkOption {
@@ -77,7 +83,7 @@ in {
 
       type = types.bool;
 
-      description = ''
+      description = lib.mdDoc ''
         Whether to allow editing the kernel command-line before
         boot. It is recommended to set this to false, as it allows
         gaining root access by passing init=/bin/sh as a kernel
@@ -90,43 +96,45 @@ in {
       default = null;
       example = 120;
       type = types.nullOr types.int;
-      description = ''
+      description = lib.mdDoc ''
         Maximum number of latest generations in the boot menu.
         Useful to prevent boot partition running out of disk space.
 
-        <literal>null</literal> means no limit i.e. all generations
+        `null` means no limit i.e. all generations
         that were not garbage collected yet.
       '';
     };
 
+    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)
       '';
     };
 
@@ -134,21 +142,21 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Make MemTest86 available from the systemd-boot menu. MemTest86 is a
           program for testing memory.  MemTest86 is an unfree program, so
-          this requires <literal>allowUnfree</literal> to be set to
-          <literal>true</literal>.
+          this requires `allowUnfree` to be set to
+          `true`.
         '';
       };
 
       entryFilename = mkOption {
         default = "memtest86.conf";
         type = types.str;
-        description = ''
-          <literal>systemd-boot</literal> orders the menu entries by the config file names,
+        description = lib.mdDoc ''
+          `systemd-boot` orders the menu entries by the config file names,
           so if you want something to appear after all the NixOS entries,
-          it should start with <filename>o</filename> or onwards.
+          it should start with {file}`o` or onwards.
         '';
       };
     };
@@ -157,9 +165,9 @@ in {
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
-          Make <literal>netboot.xyz</literal> available from the
-          <literal>systemd-boot</literal> menu. <literal>netboot.xyz</literal>
+        description = lib.mdDoc ''
+          Make `netboot.xyz` available from the
+          `systemd-boot` menu. `netboot.xyz`
           is a menu system that allows you to boot OS installers and
           utilities over the network.
         '';
@@ -168,10 +176,10 @@ in {
       entryFilename = mkOption {
         default = "o_netbootxyz.conf";
         type = types.str;
-        description = ''
-          <literal>systemd-boot</literal> orders the menu entries by the config file names,
+        description = lib.mdDoc ''
+          `systemd-boot` orders the menu entries by the config file names,
           so if you want something to appear after all the NixOS entries,
-          it should start with <filename>o</filename> or onwards.
+          it should start with {file}`o` or onwards.
         '';
       };
     };
@@ -185,15 +193,15 @@ in {
           efi /efi/memtest86/memtest86.efi
         '''; }
       '';
-      description = ''
-        Any additional entries you want added to the <literal>systemd-boot</literal> menu.
-        These entries will be copied to <filename>/boot/loader/entries</filename>.
+      description = lib.mdDoc ''
+        Any additional entries you want added to the `systemd-boot` menu.
+        These entries will be copied to {file}`/boot/loader/entries`.
         Each attribute name denotes the destination file name,
         and the corresponding attribute value is the contents of the entry.
 
-        <literal>systemd-boot</literal> orders the menu entries by the config file names,
+        `systemd-boot` orders the menu entries by the config file names,
         so if you want something to appear after all the NixOS entries,
-        it should start with <filename>o</filename> or onwards.
+        it should start with {file}`o` or onwards.
       '';
     };
 
@@ -203,10 +211,10 @@ in {
       example = literalExpression ''
         { "efi/memtest86/memtest86.efi" = "''${pkgs.memtest86-efi}/BOOTX64.efi"; }
       '';
-      description = ''
-        A set of files to be copied to <filename>/boot</filename>.
+      description = lib.mdDoc ''
+        A set of files to be copied to {file}`/boot`.
         Each attribute name denotes the destination file name in
-        <filename>/boot</filename>, while the corresponding
+        {file}`/boot`, while the corresponding
         attribute value specifies the source file.
       '';
     };
@@ -216,13 +224,13 @@ in {
 
       type = types.bool;
 
-      description = ''
-        Invoke <literal>bootctl install</literal> with the <literal>--graceful</literal> option,
+      description = lib.mdDoc ''
+        Invoke `bootctl install` with the `--graceful` option,
         which ignores errors when EFI variables cannot be written or when the EFI System Partition
         cannot be found. Currently only applies to random seed operations.
 
-        Only enable this option if <literal>systemd-boot</literal> otherwise fails to install, as the
-        scope or implication of the <literal>--graceful</literal> option may change in the future.
+        Only enable this option if `systemd-boot` otherwise fails to install, as the
+        scope or implication of the `--graceful` option may change in the future.
       '';
     };
 
@@ -291,7 +299,7 @@ in {
     ];
 
     system = {
-      build.installBootLoader = checkedSystemdBootBuilder;
+      build.installBootLoader = finalSystemdBootBuilder;
 
       boot.loader.id = "systemd-boot";
 
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 57fc02a2e322..03d03cb348e8 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -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.
@@ -417,7 +418,7 @@ let
     }
     ''}
 
-    ${optionalString (luks.fido2Support && (dev.fido2.credential != null)) ''
+    ${optionalString (luks.fido2Support && fido2luksCredentials != []) ''
 
     open_with_hardware() {
       local passsphrase
@@ -433,7 +434,7 @@ let
           echo "Please move your mouse to create needed randomness."
         ''}
           echo "Waiting for your FIDO2 device..."
-          fido2luks open ${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 +445,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
@@ -480,8 +481,8 @@ let
       ++ 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}"
     ;
   in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices));
 
@@ -496,10 +497,10 @@ in
     boot.initrd.luks.mitigateDMAAttacks = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Unless enabled, encryption keys can be easily recovered by an attacker with physical
         access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
-        More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>.
+        More information is available at <http://en.wikipedia.org/wiki/DMA_attack>.
 
         This option blacklists FireWire drivers, but doesn't remove them. You can manually
         load the drivers if you need to use a FireWire device, but don't forget to unload them!
@@ -513,7 +514,7 @@ in
           "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
           "af_alg" "algif_skcipher"
         ];
-      description = ''
+      description = lib.mdDoc ''
         A list of cryptographic kernel modules needed to decrypt the root device(s).
         The default includes all common modules.
       '';
@@ -523,7 +524,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 +533,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.
       '';
     };
@@ -548,11 +549,11 @@ in
     boot.initrd.luks.devices = mkOption {
       default = { };
       example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
-      description = ''
+      description = lib.mdDoc ''
         The encrypted disk that should be opened before the root
         filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
         setups are supported. The unencrypted devices can be accessed as
-        <filename>/dev/mapper/<replaceable>name</replaceable></filename>.
+        {file}`/dev/mapper/«name»`.
       '';
 
       type = with types; attrsOf (submodule (
@@ -563,20 +564,20 @@ 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 {
             example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
             type = types.str;
-            description = "Path of the underlying encrypted block device.";
+            description = lib.mdDoc "Path of the underlying encrypted block device.";
           };
 
           header = mkOption {
             default = null;
             example = "/root/header.img";
             type = types.nullOr types.str;
-            description = ''
+            description = lib.mdDoc ''
               The name of the file or block device that
               should be used as header for the encrypted device.
             '';
@@ -586,7 +587,7 @@ in
             default = null;
             example = "/dev/sdb1";
             type = types.nullOr types.str;
-            description = ''
+            description = lib.mdDoc ''
               The name of the file (can be a raw device or a partition) that
               should be used as the decryption key for the encrypted device. If
               not specified, you will be prompted for a passphrase instead.
@@ -597,12 +598,12 @@ in
             default = null;
             example = 4096;
             type = types.nullOr types.int;
-            description = ''
+            description = lib.mdDoc ''
               The size of the key file. Use this if only the beginning of the
               key file should be used as a key (often the case if a raw device
               or partition is used as key file). If not specified, the whole
-              <literal>keyFile</literal> will be used decryption, instead of just
-              the first <literal>keyFileSize</literal> bytes.
+              `keyFile` will be used decryption, instead of just
+              the first `keyFileSize` bytes.
             '';
           };
 
@@ -610,12 +611,12 @@ in
             default = null;
             example = 4096;
             type = types.nullOr types.int;
-            description = ''
+            description = lib.mdDoc ''
               The offset of the key file. Use this in combination with
-              <literal>keyFileSize</literal> to use part of a file as key file
+              `keyFileSize` to use part of a file as key file
               (often the case if a raw device or partition is used as a key file).
               If not specified, the key begins at the first byte of
-              <literal>keyFile</literal>.
+              `keyFile`.
             '';
           };
 
@@ -623,13 +624,13 @@ in
           preLVM = mkOption {
             default = true;
             type = types.bool;
-            description = "Whether the luksOpen will be attempted before LVM scan or after it.";
+            description = lib.mdDoc "Whether the luksOpen will be attempted before LVM scan or after it.";
           };
 
           allowDiscards = mkOption {
             default = false;
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Whether to allow TRIM requests to the underlying device. This option
               has security implications; please read the LUKS documentation before
               activating it.
@@ -641,10 +642,10 @@ in
           bypassWorkqueues = mkOption {
             default = false;
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Whether to bypass dm-crypt's internal read and write workqueues.
               Enabling this should improve performance on SSDs; see
-              <link xlink:href="https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance">here</link>
+              [here](https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance)
               for more information. Needs Linux 5.9 or later.
             '';
           };
@@ -652,7 +653,7 @@ in
           fallbackToPassword = mkOption {
             default = false;
             type = types.bool;
-            description = ''
+            description = lib.mdDoc ''
               Whether to fallback to interactive passphrase prompt if the keyfile
               cannot be found. This will prevent unattended boot should the keyfile
               go missing.
@@ -661,7 +662,7 @@ in
 
           gpgCard = mkOption {
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
               If null (the default), GPG-Smartcard will be disabled for this device.
             '';
@@ -671,17 +672,17 @@ in
                 gracePeriod = mkOption {
                   default = 10;
                   type = types.int;
-                  description = "Time in seconds to wait for the GPG Smartcard.";
+                  description = lib.mdDoc "Time in seconds to wait for the GPG Smartcard.";
                 };
 
                 encryptedPass = mkOption {
                   type = types.path;
-                  description = "Path to the GPG encrypted passphrase.";
+                  description = lib.mdDoc "Path to the GPG encrypted passphrase.";
                 };
 
                 publicKey = mkOption {
                   type = types.path;
-                  description = "Path to the Public Key.";
+                  description = lib.mdDoc "Path to the Public Key.";
                 };
               };
             });
@@ -692,29 +693,40 @@ in
               default = null;
               example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
               type = types.nullOr types.str;
-              description = "The FIDO2 credential ID.";
+              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;
-              description = "Time in seconds to wait for the FIDO2 key.";
+              description = lib.mdDoc "Time in seconds to wait for the FIDO2 key.";
             };
 
             passwordLess = mkOption {
               default = false;
               type = types.bool;
-              description = ''
+              description = lib.mdDoc ''
                 Defines whatever to use an empty string as a default salt.
 
-                Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>.
+                Enable only when your device is PIN protected, such as [Trezor](https://trezor.io/).
               '';
             };
           };
 
           yubikey = mkOption {
             default = null;
-            description = ''
+            description = lib.mdDoc ''
               The options to use for this LUKS device in YubiKey-PBA.
               If null (the default), YubiKey-PBA will be disabled for this device.
             '';
@@ -724,37 +736,37 @@ in
                 twoFactor = mkOption {
                   default = true;
                   type = types.bool;
-                  description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
+                  description = lib.mdDoc "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
                 };
 
                 slot = mkOption {
                   default = 2;
                   type = types.int;
-                  description = "Which slot on the YubiKey to challenge.";
+                  description = lib.mdDoc "Which slot on the YubiKey to challenge.";
                 };
 
                 saltLength = mkOption {
                   default = 16;
                   type = types.int;
-                  description = "Length of the new salt in byte (64 is the effective maximum).";
+                  description = lib.mdDoc "Length of the new salt in byte (64 is the effective maximum).";
                 };
 
                 keyLength = mkOption {
                   default = 64;
                   type = types.int;
-                  description = "Length of the LUKS slot key derived with PBKDF2 in byte.";
+                  description = lib.mdDoc "Length of the LUKS slot key derived with PBKDF2 in byte.";
                 };
 
                 iterationStep = mkOption {
                   default = 0;
                   type = types.int;
-                  description = "How much the iteration count for PBKDF2 is increased at each successful authentication.";
+                  description = lib.mdDoc "How much the iteration count for PBKDF2 is increased at each successful authentication.";
                 };
 
                 gracePeriod = mkOption {
                   default = 10;
                   type = types.int;
-                  description = "Time in seconds to wait for the YubiKey.";
+                  description = lib.mdDoc "Time in seconds to wait for the YubiKey.";
                 };
 
                 /* TODO: Add to the documentation of the current module:
@@ -765,7 +777,7 @@ in
                   device = mkOption {
                     default = "/dev/sda1";
                     type = types.path;
-                    description = ''
+                    description = lib.mdDoc ''
                       An unencrypted device that will temporarily be mounted in stage-1.
                       Must contain the current salt to create the challenge for this LUKS device.
                     '';
@@ -774,13 +786,13 @@ in
                   fsType = mkOption {
                     default = "vfat";
                     type = types.str;
-                    description = "The filesystem of the unencrypted device.";
+                    description = lib.mdDoc "The filesystem of the unencrypted device.";
                   };
 
                   path = mkOption {
                     default = "/crypt-storage/default";
                     type = types.str;
-                    description = ''
+                    description = lib.mdDoc ''
                       Absolute path of the salt on the unencrypted device with
                       that device's root directory as "/".
                     '';
@@ -797,7 +809,7 @@ in
               mkdir -p /tmp/persistent
               mount -t zfs rpool/safe/persistent /tmp/persistent
             '';
-            description = ''
+            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.
             '';
@@ -809,7 +821,7 @@ in
             example = ''
               umount /tmp/persistent
             '';
-            description = ''
+            description = lib.mdDoc ''
               Commands that should be run right after we have mounted our LUKS device.
             '';
           };
@@ -819,7 +831,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.
@@ -832,7 +844,7 @@ in
     boot.initrd.luks.gpgSupport = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Enables support for authenticating with a GPG encrypted password.
       '';
     };
@@ -840,7 +852,7 @@ in
     boot.initrd.luks.yubikeySupport = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
             Enables support for authenticating with a YubiKey on LUKS devices.
             See the NixOS wiki for information on how to properly setup a LUKS device
             and a YubiKey to work with this feature.
@@ -850,7 +862,7 @@ in
     boot.initrd.luks.fido2Support = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Enables support for authenticating with FIDO2 devices.
       '';
     };
@@ -893,9 +905,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;
@@ -992,6 +1006,7 @@ in
       ];
       storePaths = [
         "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup"
+        "${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-cryptsetup-generator"
       ];
 
     };
diff --git a/nixos/modules/system/boot/modprobe.nix b/nixos/modules/system/boot/modprobe.nix
index e683d1817297..54bb7ea9ddd7 100644
--- a/nixos/modules/system/boot/modprobe.nix
+++ b/nixos/modules/system/boot/modprobe.nix
@@ -7,12 +7,15 @@ with lib;
   ###### interface
 
   options = {
+    boot.modprobeConfig.enable = mkEnableOption (lib.mdDoc "modprobe config. This is useful for systemds like containers which do not require a kernel.") // {
+      default = true;
+    };
 
     boot.blacklistedKernelModules = mkOption {
       type = types.listOf types.str;
       default = [];
       example = [ "cirrusfb" "i2c_piix4" ];
-      description = ''
+      description = lib.mdDoc ''
         List of names of kernel modules that should not be loaded
         automatically by the hardware probing code.
       '';
@@ -24,12 +27,11 @@ with lib;
         ''
           options parport_pc io=0x378 irq=7 dma=1
         '';
-      description = ''
+      description = lib.mdDoc ''
         Any additional configuration to be appended to the generated
-        <filename>modprobe.conf</filename>.  This is typically used to
+        {file}`modprobe.conf`.  This is typically used to
         specify module options.  See
-        <citerefentry><refentrytitle>modprobe.d</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        {manpage}`modprobe.d(5)` for details.
       '';
       type = types.lines;
     };
@@ -39,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";
 
@@ -52,7 +54,7 @@ with lib;
       '';
     environment.etc."modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
 
-    environment.etc."modprobe.d/systemd.conf".source = "${pkgs.systemd}/lib/modprobe.d/systemd.conf";
+    environment.etc."modprobe.d/systemd.conf".source = "${config.systemd.package}/lib/modprobe.d/systemd.conf";
 
     environment.systemPackages = [ pkgs.kmod ];
 
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index d1a6f46bfc40..188f2f64dc84 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -451,15 +451,21 @@ let
           "Multicast"
           "AllMulticast"
           "Unmanaged"
+          "Group"
           "RequiredForOnline"
+          "RequiredFamilyForOnline"
           "ActivationPolicy"
+          "Promiscuous"
         ])
         (assertMacAddress "MACAddress")
         (assertByteFormat "MTUBytes")
         (assertValueOneOf "ARP" boolValues)
         (assertValueOneOf "Multicast" boolValues)
         (assertValueOneOf "AllMulticast" boolValues)
+        (assertValueOneOf "Promiscuous" boolValues)
         (assertValueOneOf "Unmanaged" boolValues)
+        (assertInt "Group")
+        (assertRange "Group" 0 2147483647)
         (assertValueOneOf "RequiredForOnline" (boolValues ++ [
           "missing"
           "off"
@@ -471,6 +477,12 @@ let
           "enslaved"
           "routable"
         ]))
+        (assertValueOneOf "RequiredFamilyForOnline" [
+          "ipv4"
+          "ipv6"
+          "both"
+          "any"
+        ])
         (assertValueOneOf "ActivationPolicy" ([
           "up"
           "always-up"
@@ -489,7 +501,6 @@ let
           "LinkLocalAddressing"
           "IPv4LLRoute"
           "DefaultRouteOnDevice"
-          "IPv6Token"
           "LLMNR"
           "MulticastDNS"
           "DNSOverTLS"
@@ -514,7 +525,7 @@ let
           "IPv6ProxyNDP"
           "IPv6ProxyNDPAddress"
           "IPv6SendRA"
-          "DHCPv6PrefixDelegation"
+          "DHCPPrefixDelegation"
           "IPv6MTUBytes"
           "Bridge"
           "Bond"
@@ -557,12 +568,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"]))
       ];
 
@@ -574,6 +584,7 @@ let
           "Label"
           "PreferredLifetime"
           "Scope"
+          "RouteMetric"
           "HomeAddress"
           "DuplicateAddressDetection"
           "ManageTemporaryAddress"
@@ -582,6 +593,7 @@ let
         ])
         (assertHasField "Address")
         (assertValueOneOf "PreferredLifetime" ["forever" "infinity" "0" 0])
+        (assertInt "RouteMetric")
         (assertValueOneOf "HomeAddress" boolValues)
         (assertValueOneOf "DuplicateAddressDetection" ["ipv4" "ipv6" "both" "none"])
         (assertValueOneOf "ManageTemporaryAddress" boolValues)
@@ -607,6 +619,7 @@ let
           "User"
           "SuppressPrefixLength"
           "Type"
+          "SuppressInterfaceGroup"
         ])
         (assertInt "TypeOfService")
         (assertRange "TypeOfService" 0 255)
@@ -620,6 +633,7 @@ let
         (assertInt "SuppressPrefixLength")
         (assertRange "SuppressPrefixLength" 0 128)
         (assertValueOneOf "Type" ["blackhole" "unreachable" "prohibit"])
+        (assertRange "SuppressInterfaceGroup" 0 2147483647)
       ];
 
       sectionRoute = checkUnitConfig "Route" [
@@ -699,6 +713,9 @@ let
           "BlackList"
           "RequestOptions"
           "SendOption"
+          "FallbackLeaseLifetimeSec"
+          "Label"
+          "Use6RD"
         ])
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "RoutesToDNS" boolValues)
@@ -721,6 +738,8 @@ let
         (assertPort "ListenPort")
         (assertValueOneOf "SendRelease" boolValues)
         (assertValueOneOf "SendDecline" boolValues)
+        (assertValueOneOf "FallbackLeaseLifetimeSec" ["forever" "infinity"])
+        (assertValueOneOf "Use6RD" boolValues)
       ];
 
       sectionDHCPv6 = checkUnitConfig "DHCPv6" [
@@ -733,7 +752,6 @@ let
           "MUDURL"
           "RequestOptions"
           "SendVendorOption"
-          "ForceDHCPv6PDOtherInformation"
           "PrefixDelegationHint"
           "WithoutRA"
           "SendOption"
@@ -742,27 +760,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" [
@@ -779,6 +803,11 @@ let
           "RouteDenyList"
           "RouteAllowList"
           "DHCPv6Client"
+          "RouteMetric"
+          "UseMTU"
+          "UseGateway"
+          "UseRoutePrefix"
+          "Token"
         ])
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
@@ -786,14 +815,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"
@@ -807,10 +841,15 @@ let
           "EmitLPR"
           "LPR"
           "EmitRouter"
+          "Router"
           "EmitTimezone"
           "Timezone"
           "SendOption"
           "SendVendorOption"
+          "BindToInterface"
+          "RelayTarget"
+          "RelayAgentCircuitId"
+          "RelayAgentRemoteId"
         ])
         (assertInt "PoolOffset")
         (assertMinimum "PoolOffset" 0)
@@ -824,6 +863,7 @@ let
         (assertValueOneOf "EmitLPR" boolValues)
         (assertValueOneOf "EmitRouter" boolValues)
         (assertValueOneOf "EmitTimezone" boolValues)
+        (assertValueOneOf "BindToInterface" boolValues)
       ];
 
       sectionIPv6SendRA = checkUnitConfig "IPv6SendRA" [
@@ -832,6 +872,7 @@ let
           "OtherInformation"
           "RouterLifetimeSec"
           "RouterPreference"
+          "UplinkInterface"
           "EmitDNS"
           "DNS"
           "EmitDomains"
@@ -852,11 +893,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"
@@ -875,8 +926,10 @@ let
     enable = mkOption {
       default = true;
       type = types.bool;
-      description = ''
-        Whether to manage network configuration using <command>systemd-network</command>.
+      description = lib.mdDoc ''
+        Whether to manage network configuration using {command}`systemd-network`.
+
+        This also enables {option}`systemd.networkd.enable`.
       '';
     };
 
@@ -884,12 +937,12 @@ let
       default = {};
       example = { Name = "eth0"; };
       type = types.attrsOf unitOption;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Match]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-        <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        `[Match]` section of the unit.  See
+        {manpage}`systemd.link(5)`
+        {manpage}`systemd.netdev(5)`
+        {manpage}`systemd.network(5)`
         for details.
       '';
     };
@@ -897,7 +950,7 @@ let
     extraConfig = mkOption {
       default = "";
       type = types.lines;
-      description = "Extra configuration append to unit";
+      description = lib.mdDoc "Extra configuration append to unit";
     };
   };
 
@@ -906,11 +959,10 @@ let
       default = {};
       example = { SpeedMeter = true; ManageForeignRoutingPolicyRules = false; };
       type = types.addCheck (types.attrsOf unitOption) check.global.sectionNetwork;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Network]</literal> section of the networkd config.
-        See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Network]` section of the networkd config.
+        See {manpage}`networkd.conf(5)` for details.
       '';
     };
 
@@ -918,11 +970,10 @@ let
       default = {};
       example = { DUIDType = "vendor"; };
       type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv4;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCPv4]</literal> section of the networkd config.
-        See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[DHCPv4]` section of the networkd config.
+        See {manpage}`networkd.conf(5)` for details.
       '';
     };
 
@@ -930,11 +981,10 @@ let
       default = {};
       example = { DUIDType = "vendor"; };
       type = types.addCheck (types.attrsOf unitOption) check.global.sectionDHCPv6;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCPv6]</literal> section of the networkd config.
-        See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[DHCPv6]` section of the networkd config.
+        See {manpage}`networkd.conf(5)` for details.
       '';
     };
   };
@@ -944,8 +994,8 @@ let
     enable = mkOption {
       default = true;
       type = types.bool;
-      description = ''
-        Whether to enable this .link unit. It's handled by udev no matter if <command>systemd-networkd</command> is enabled or not
+      description = lib.mdDoc ''
+        Whether to enable this .link unit. It's handled by udev no matter if {command}`systemd-networkd` is enabled or not
       '';
     };
 
@@ -953,11 +1003,10 @@ let
       default = {};
       example = { MACAddress = "00:ff:ee:aa:cc:dd"; };
       type = types.addCheck (types.attrsOf unitOption) check.link.sectionLink;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Link]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.link</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Link]` section of the unit.  See
+        {manpage}`systemd.link(5)` for details.
       '';
     };
 
@@ -968,11 +1017,10 @@ let
       wireguardPeerConfig = mkOption {
         default = {};
         type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuardPeer;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[WireGuardPeer]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.network</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[WireGuardPeer]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
         '';
       };
     };
@@ -983,11 +1031,10 @@ let
     netdevConfig = mkOption {
       example = { Name = "mybridge"; Kind = "bridge"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionNetdev;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Netdev]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Netdev]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -995,11 +1042,10 @@ let
       default = {};
       example = { Id = 4; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVLAN;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[VLAN]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[VLAN]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1007,22 +1053,20 @@ let
       default = {};
       example = { Mode = "private"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionMACVLAN;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[MACVLAN]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[MACVLAN]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
     vxlanConfig = mkOption {
       default = {};
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVXLAN;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[VXLAN]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[VXLAN]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1030,11 +1074,10 @@ let
       default = {};
       example = { Remote = "192.168.1.1"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTunnel;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Tunnel]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Tunnel]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1042,11 +1085,10 @@ let
       default = { };
       example = { Port = 9001; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionFooOverUDP;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[FooOverUDP]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[FooOverUDP]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1054,11 +1096,10 @@ let
       default = {};
       example = { Name = "veth2"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionPeer;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Peer]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Peer]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1066,11 +1107,10 @@ let
       default = {};
       example = { User = "openvpn"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTun;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Tun]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Tun]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1078,11 +1118,10 @@ let
       default = {};
       example = { User = "openvpn"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionTap;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Tap]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Tap]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1094,13 +1133,12 @@ let
         FirewallMark = 42;
       };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWireGuard;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[WireGuard]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
-        Use <literal>PrivateKeyFile</literal> instead of
-        <literal>PrivateKey</literal>: the nix store is
+        `[WireGuard]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
+        Use `PrivateKeyFile` instead of
+        `PrivateKey`: the nix store is
         world-readable.
       '';
     };
@@ -1115,13 +1153,12 @@ let
         PersistentKeepalive = 15;
       };}];
       type = with types; listOf (submodule wireguardPeerOptions);
-      description = ''
+      description = lib.mdDoc ''
         Each item in this array specifies an option in the
-        <literal>[WireGuardPeer]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
-        Use <literal>PresharedKeyFile</literal> instead of
-        <literal>PresharedKey</literal>: the nix store is
+        `[WireGuardPeer]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
+        Use `PresharedKeyFile` instead of
+        `PresharedKey`: the nix store is
         world-readable.
       '';
     };
@@ -1130,11 +1167,10 @@ let
       default = {};
       example = { Mode = "802.3ad"; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBond;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Bond]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Bond]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1142,11 +1178,10 @@ let
       default = {};
       example = { InterfaceId = 1; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionXfrm;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Xfrm]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Xfrm]` section of the unit.  See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1154,14 +1189,12 @@ let
       default = {};
       example = { Table = 2342; };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionVRF;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[VRF]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[VRF]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
         A detailed explanation about how VRFs work can be found in the
-        <link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel
-        docs</link>.
+        [kernel docs](https://www.kernel.org/doc/Documentation/networking/vrf.txt).
       '';
     };
 
@@ -1172,11 +1205,10 @@ let
         RoutingAlgorithm = "batman-v";
       };
       type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionBatmanAdvanced;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[BatmanAdvanced]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[BatmanAdvanced]` section of the unit. See
+        {manpage}`systemd.netdev(5)` for details.
       '';
     };
 
@@ -1187,11 +1219,10 @@ let
       addressConfig = mkOption {
         example = { Address = "192.168.0.100/24"; };
         type = types.addCheck (types.attrsOf unitOption) check.network.sectionAddress;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Address]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.network</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Address]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
         '';
       };
     };
@@ -1203,11 +1234,10 @@ let
         default = { };
         example = { Table = 10; IncomingInterface = "eth1"; Family = "both"; };
         type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoutingPolicyRule;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[RoutingPolicyRule]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.network</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[RoutingPolicyRule]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
         '';
       };
     };
@@ -1219,11 +1249,10 @@ let
         default = {};
         example = { Gateway = "192.168.0.1"; };
         type = types.addCheck (types.attrsOf unitOption) check.network.sectionRoute;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Route]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.network</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Route]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
         '';
       };
     };
@@ -1235,11 +1264,25 @@ let
         default = {};
         example = { Prefix = "fd00::/64"; };
         type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6Prefix;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[IPv6Prefix]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.network</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[IPv6Prefix]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
+        '';
+      };
+    };
+  };
+
+  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.
         '';
       };
     };
@@ -1251,14 +1294,13 @@ let
         default = {};
         example = { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; };
         type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServerStaticLease;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[DHCPServerStaticLease]</literal> section of the unit.  See
-          <citerefentry><refentrytitle>systemd.network</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[DHCPServerStaticLease]` section of the unit.  See
+          {manpage}`systemd.network(5)` for details.
 
           Make sure to configure the corresponding client interface to use
-          <literal>ClientIdentifier=mac</literal>.
+          `ClientIdentifier=mac`.
         '';
       };
     };
@@ -1270,11 +1312,10 @@ let
       default = {};
       example = { Unmanaged = true; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionLink;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Link]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Link]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
@@ -1282,11 +1323,10 @@ let
       default = {};
       example = { Description = "My Network"; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionNetwork;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[Network]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[Network]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
@@ -1301,11 +1341,10 @@ let
       default = {};
       example = { UseDNS = true; UseRoutes = true; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv4;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCPv4]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[DHCPv4]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
@@ -1313,23 +1352,26 @@ let
       default = {};
       example = { UseDNS = true; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPv6;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCPv6]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[DHCPv6]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
     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;
-      description = ''
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPPrefixDelegation;
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCPv6PrefixDelegation]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[DHCPPrefixDelegation]` section of the unit. See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
@@ -1337,11 +1379,10 @@ let
       default = {};
       example = { UseDNS = true; DHCPv6Client = "always"; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6AcceptRA;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[IPv6AcceptRA]</literal> section of the unit. See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[IPv6AcceptRA]` section of the unit. See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
@@ -1349,11 +1390,10 @@ let
       default = {};
       example = { PoolOffset = 50; EmitDNS = false; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionDHCPServer;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[DHCPServer]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[DHCPServer]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
@@ -1368,40 +1408,47 @@ let
       default = {};
       example = { EmitDNS = true; Managed = true; OtherInformation = true; };
       type = types.addCheck (types.attrsOf unitOption) check.network.sectionIPv6SendRA;
-      description = ''
+      description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
-        <literal>[IPv6SendRA]</literal> section of the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        `[IPv6SendRA]` section of the unit.  See
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
     dhcpServerStaticLeases = mkOption {
       default = [];
-      example = [ { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; } ];
+      example = [ { dhcpServerStaticLeaseConfig = { MACAddress = "65:43:4a:5b:d8:5f"; Address = "192.168.1.42"; }; } ];
       type = with types; listOf (submodule dhcpServerStaticLeaseOptions);
-      description = ''
+      description = lib.mdDoc ''
         A list of DHCPServerStaticLease sections to be added to the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
     ipv6Prefixes = mkOption {
       default = [];
-      example = [ { AddressAutoconfiguration = true; OnLink = true; } ];
+      example = [ { ipv6PrefixConfig = { AddressAutoconfiguration = true; OnLink = true; }; } ];
       type = with types; listOf (submodule ipv6PrefixOptions);
-      description = ''
+      description = lib.mdDoc ''
         A list of ipv6Prefix sections to be added to the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        {manpage}`systemd.network(5)` for details.
+      '';
+    };
+
+    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.
       '';
     };
 
     name = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         The name of the network interface to match against.
       '';
     };
@@ -1409,7 +1456,7 @@ let
     DHCP = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable DHCP on the interfaces matched.
       '';
     };
@@ -1417,7 +1464,7 @@ let
     domains = mkOption {
       type = types.nullOr (types.listOf types.str);
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         A list of domains to pass to the network config.
       '';
     };
@@ -1425,150 +1472,135 @@ let
     address = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of addresses to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     gateway = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of gateways to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     dns = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of dns servers to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     ntp = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of ntp servers to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     bridge = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of bridge interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     bond = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of bond interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     vrf = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of vrf interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     vlan = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of vlan interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     macvlan = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of macvlan interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     vxlan = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of vxlan interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     tunnel = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of tunnel interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     xfrm = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of xfrm interfaces to be added to the network section of the
-        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        unit.  See {manpage}`systemd.network(5)` for details.
       '';
     };
 
     addresses = mkOption {
       default = [ ];
       type = with types; listOf (submodule addressOptions);
-      description = ''
+      description = lib.mdDoc ''
         A list of address sections to be added to the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
     routingPolicyRules = mkOption {
       default = [ ];
       type = with types; listOf (submodule routingPolicyRulesOptions);
-      description = ''
+      description = lib.mdDoc ''
         A list of routing policy rules sections to be added to the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
     routes = mkOption {
       default = [ ];
       type = with types; listOf (submodule routeOptions);
-      description = ''
+      description = lib.mdDoc ''
         A list of route sections to be added to the unit.  See
-        <citerefentry><refentrytitle>systemd.network</refentrytitle>
-        <manvolnum>5</manvolnum></citerefentry> for details.
+        {manpage}`systemd.network(5)` for details.
       '';
     };
 
@@ -1593,10 +1625,9 @@ let
         default = {};
         example = { foo = 27; };
         type = with types; attrsOf int;
-        description = ''
+        description = lib.mdDoc ''
           Defines route table names as an attrset of name to number.
-          See <citerefentry><refentrytitle>networkd.conf</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          See {manpage}`networkd.conf(5)` for details.
         '';
       };
 
@@ -1604,7 +1635,7 @@ let
         default = true;
         example = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If true and routeTables are set, then the specified route tables
           will also be installed into /etc/iproute2/rt_tables.
         '';
@@ -1785,9 +1816,9 @@ let
           [DHCPv6]
           ${attrsToSection def.dhcpV6Config}
         ''
-        + optionalString (def.dhcpV6PrefixDelegationConfig != { }) ''
-          [DHCPv6PrefixDelegation]
-          ${attrsToSection def.dhcpV6PrefixDelegationConfig}
+        + optionalString (def.dhcpPrefixDelegationConfig != { }) ''
+          [DHCPPrefixDelegation]
+          ${attrsToSection def.dhcpPrefixDelegationConfig}
         ''
         + optionalString (def.ipv6AcceptRAConfig != { }) ''
           [IPv6AcceptRA]
@@ -1805,6 +1836,10 @@ let
           [IPv6Prefix]
           ${attrsToSection x.ipv6PrefixConfig}
         '')
+        + flip concatMapStrings def.ipv6RoutePrefixes (x: ''
+          [IPv6RoutePrefix]
+          ${attrsToSection x.ipv6RoutePrefixConfig}
+        '')
         + flip concatMapStrings def.dhcpServerStaticLeases (x: ''
           [DHCPServerStaticLease]
           ${attrsToSection x.dhcpServerStaticLeaseConfig}
@@ -1824,7 +1859,7 @@ in
     systemd.network.enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable networkd or not.
       '';
     };
@@ -1832,29 +1867,29 @@ in
     systemd.network.links = mkOption {
       default = {};
       type = with types; attrsOf (submodule [ { options = linkOptions; } ]);
-      description = "Definition of systemd network links.";
+      description = lib.mdDoc "Definition of systemd network links.";
     };
 
     systemd.network.netdevs = mkOption {
       default = {};
       type = with types; attrsOf (submodule [ { options = netdevOptions; } ]);
-      description = "Definition of systemd network devices.";
+      description = lib.mdDoc "Definition of systemd network devices.";
     };
 
     systemd.network.networks = mkOption {
       default = {};
       type = with types; attrsOf (submodule [ { options = networkOptions; } networkConfig ]);
-      description = "Definition of systemd networks.";
+      description = lib.mdDoc "Definition of systemd networks.";
     };
 
     systemd.network.config = mkOption {
       default = {};
       type = with types; submodule [ { options = networkdOptions; } networkdConfig ];
-      description = "Definition of global systemd network config.";
+      description = lib.mdDoc "Definition of global systemd network config.";
     };
 
     systemd.network.units = mkOption {
-      description = "Definition of networkd units.";
+      description = lib.mdDoc "Definition of networkd units.";
       default = {};
       internal = true;
       type = with types; attrsOf (submodule (
@@ -1867,8 +1902,22 @@ in
     };
 
     systemd.network.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 = ''
+        description = lib.mdDoc ''
           Whether to consider the network online when any interface is online, as opposed to all of them.
           This is useful on portable machines with a wired and a wireless interface, for example.
         '';
@@ -1877,7 +1926,7 @@ in
       };
 
       ignoredInterfaces = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Network interfaces to be ignored when deciding if the system is online.
         '';
         type = with types; listOf str;
@@ -1886,7 +1935,7 @@ in
       };
 
       timeout = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Time to wait for the network to come online, in seconds. Set to 0 to disable.
         '';
         type = types.ints.unsigned;
@@ -1895,13 +1944,11 @@ in
       };
 
       extraArgs = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Extra command-line arguments to pass to systemd-networkd-wait-online.
-          These also affect per-interface <literal>systemd-network-wait-online@</literal> services.
+          These also affect per-interface `systemd-network-wait-online@` services.
 
-          See <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.service.html">
-          <citerefentry><refentrytitle>systemd-networkd-wait-online.service</refentrytitle><manvolnum>8</manvolnum>
-          </citerefentry></link> for all available options.
+          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.
         '';
         type = with types; listOf str;
         default = [];
@@ -1950,6 +1997,7 @@ in
       };
 
       systemd.services.systemd-networkd-wait-online = {
+        inherit (cfg.wait-online) enable;
         wantedBy = [ "network-online.target" ];
         serviceConfig.ExecStart = [
           ""
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index 78ae8e9d20b7..9b6472fea429 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -4,7 +4,10 @@ with lib;
 
 let
 
-  inherit (pkgs) plymouth nixos-icons;
+  inherit (pkgs) nixos-icons;
+  plymouth = pkgs.plymouth.override {
+    systemd = config.boot.initrd.systemd.package;
+  };
 
   cfg = config.boot.plymouth;
   opt = options.boot.plymouth;
@@ -59,26 +62,26 @@ 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";
         defaultText = literalExpression ''"''${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf"'';
         type = types.path;
-        description = ''
+        description = lib.mdDoc ''
           Font file made available for displaying text on the splash screen.
         '';
       };
 
       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 = ''
+        description = lib.mdDoc ''
           Extra theme packages for plymouth.
         '';
       };
@@ -86,7 +89,7 @@ in
       theme = mkOption {
         default = "bgrt";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Splash screen theme.
         '';
       };
@@ -99,7 +102,7 @@ in
           url = "https://nixos.org/logo/nixos-hires.png";
           sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
         }'';
-        description = ''
+        description = lib.mdDoc ''
           Logo which is displayed on the splash screen.
         '';
       };
@@ -107,8 +110,8 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
-          Literal string to append to <literal>configFile</literal>
+        description = lib.mdDoc ''
+          Literal string to append to `configFile`
           and the config file generated by the plymouth module.
         '';
       };
@@ -143,7 +146,93 @@ in
     systemd.services.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
     systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "multi-user.target" ];
 
-    boot.initrd.extraUtilsCommands = ''
+    boot.initrd.systemd = {
+      extraBin.plymouth = "${plymouth}/bin/plymouth"; # for the recovery shell
+      storePaths = [
+        "${lib.getBin config.boot.initrd.systemd.package}/bin/systemd-tty-ask-password-agent"
+        "${plymouth}/bin/plymouthd"
+        "${plymouth}/sbin/plymouthd"
+      ];
+      packages = [ plymouth ]; # systemd units
+      contents = {
+        # Files
+        "/etc/plymouth/plymouthd.conf".source = configFile;
+        "/etc/plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults";
+        "/etc/plymouth/logo.png".source = cfg.logo;
+        # Directories
+        "/etc/plymouth/plugins".source = pkgs.runCommand "plymouth-initrd-plugins" {} ''
+          # Check if the actual requested theme is here
+          if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
+              echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
+              exit 1
+          fi
+
+          moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
+
+          mkdir -p $out/renderers
+          # module might come from a theme
+          cp ${themesEnv}/lib/plymouth/{text,details,label,$moduleName}.so $out
+          cp ${plymouth}/lib/plymouth/renderers/{drm,frame-buffer}.so $out/renderers
+        '';
+        "/etc/plymouth/themes".source = pkgs.runCommand "plymouth-initrd-themes" {} ''
+          # Check if the actual requested theme is here
+          if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
+              echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
+              exit 1
+          fi
+
+          mkdir $out
+          cp -r ${themesEnv}/share/plymouth/themes/${cfg.theme} $out
+          # Copy more themes if the theme depends on others
+          for theme in $(grep -hRo '/etc/plymouth/themes/.*$' $out | xargs -n1 basename); do
+              if [[ -d "${themesEnv}/share/plymouth/themes/$theme" ]]; then
+                  if [[ ! -d "$out/$theme" ]]; then
+                    echo "Adding dependent theme: $theme"
+                    cp -r "${themesEnv}/share/plymouth/themes/$theme" $out
+                  fi
+              else
+                echo "Missing theme dependency: $theme"
+              fi
+          done
+        '';
+
+        # Fonts
+        "/etc/plymouth/fonts".source = pkgs.runCommand "plymouth-initrd-fonts" {} ''
+          mkdir -p $out
+          cp ${cfg.font} $out
+        '';
+        "/etc/fonts/fonts.conf".text = ''
+          <?xml version="1.0"?>
+          <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
+          <fontconfig>
+              <dir>/etc/plymouth/fonts</dir>
+          </fontconfig>
+        '';
+      };
+      # Properly enable units. These are the units that arch copies
+      services = {
+        plymouth-halt.wantedBy = [ "halt.target" ];
+        plymouth-kexec.wantedBy = [ "kexec.target" ];
+        plymouth-poweroff.wantedBy = [ "poweroff.target" ];
+        plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
+        plymouth-quit.wantedBy = [ "multi-user.target" ];
+        plymouth-read-write.wantedBy = [ "sysinit.target" ];
+        plymouth-reboot.wantedBy = [ "reboot.target" ];
+        plymouth-start.wantedBy = [ "initrd-switch-root.target" "sysinit.target" ];
+        plymouth-switch-root-initramfs.wantedBy = [ "halt.target" "kexec.target" "plymouth-switch-root-initramfs.service" "poweroff.target" "reboot.target" ];
+        plymouth-switch-root.wantedBy = [ "initrd-switch-root.target" ];
+      };
+    };
+
+    # Insert required udev rules. We take stage 2 systemd because the udev
+    # rules are only generated when building with logind.
+    boot.initrd.services.udev.packages = [ (pkgs.runCommand "initrd-plymouth-udev-rules" {} ''
+      mkdir -p $out/etc/udev/rules.d
+      cp ${config.systemd.package.out}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out/etc/udev/rules.d
+      sed -i '/loginctl/d' $out/etc/udev/rules.d/71-seat.rules
+    '') ];
+
+    boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${plymouth}/bin/plymouth
       copy_bin_and_libs ${plymouth}/bin/plymouthd
 
@@ -198,18 +287,18 @@ in
       EOF
     '';
 
-    boot.initrd.extraUtilsCommandsTest = ''
+    boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
       $out/bin/plymouthd --help >/dev/null
       $out/bin/plymouth --help >/dev/null
     '';
 
-    boot.initrd.extraUdevRulesCommands = ''
+    boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       cp ${config.systemd.package}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out
       sed -i '/loginctl/d' $out/71-seat.rules
     '';
 
     # We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen.
-    boot.initrd.preLVMCommands = mkAfter ''
+    boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkAfter ''
       mkdir -p /etc/plymouth
       mkdir -p /run/plymouth
       ln -s ${configFile} /etc/plymouth/plymouthd.conf
@@ -221,16 +310,16 @@ in
 
       plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session
       plymouth show-splash
-    '';
+    '');
 
-    boot.initrd.postMountCommands = ''
+    boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       plymouth update-root-fs --new-root-dir="$targetRoot"
     '';
 
     # `mkBefore` to ensure that any custom prompts would be visible.
-    boot.initrd.preFailCommands = mkBefore ''
+    boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore ''
       plymouth quit --wait
-    '';
+    '');
 
   };
 
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index 21d3fab2f35d..0ab2a875975d 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 let
@@ -15,7 +15,7 @@ in
     services.resolved.enable = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the systemd DNS resolver daemon.
       '';
     };
@@ -24,7 +24,7 @@ in
       default = [ ];
       example = [ "8.8.8.8" "2001:4860:4860::8844" ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of IPv4 and IPv6 addresses to use as the fallback DNS servers.
         If this option is empty, a compiled-in list of DNS servers is used instead.
       '';
@@ -35,7 +35,7 @@ in
       defaultText = literalExpression "config.networking.search";
       example = [ "example.com" ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         A list of domains. These domains are used as search suffixes
         when resolving single-label host names (domain names which
         contain no dot), in order to qualify them into fully-qualified
@@ -43,7 +43,7 @@ in
 
         For compatibility reasons, if this setting is not specified,
         the search domains listed in
-        <filename>/etc/resolv.conf</filename> are used instead, if
+        {file}`/etc/resolv.conf` are used instead, if
         that file exists and any domains are configured in it.
       '';
     };
@@ -52,32 +52,14 @@ in
       default = "true";
       example = "false";
       type = types.enum [ "true" "resolve" "false" ];
-      description = ''
+      description = lib.mdDoc ''
         Controls Link-Local Multicast Name Resolution support
         (RFC 4795) on the local host.
 
         If set to
-
-        <variablelist>
-        <varlistentry>
-          <term><literal>"true"</literal></term>
-          <listitem><para>
-            Enables full LLMNR responder and resolver support.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"false"</literal></term>
-          <listitem><para>
-            Disables both.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"resolve"</literal></term>
-          <listitem><para>
-            Only resolution support is enabled, but responding is disabled.
-          </para></listitem>
-        </varlistentry>
-        </variablelist>
+        - `"true"`: Enables full LLMNR responder and resolver support.
+        - `"false"`: Disables both.
+        - `"resolve"`: Only resolution support is enabled, but responding is disabled.
       '';
     };
 
@@ -85,21 +67,14 @@ in
       default = "allow-downgrade";
       example = "true";
       type = types.enum [ "true" "allow-downgrade" "false" ];
-      description = ''
+      description = lib.mdDoc ''
         If set to
-        <variablelist>
-        <varlistentry>
-          <term><literal>"true"</literal></term>
-          <listitem><para>
+        - `"true"`:
             all DNS lookups are DNSSEC-validated locally (excluding
             LLMNR and Multicast DNS). Note that this mode requires a
             DNS server that supports DNSSEC. If the DNS server does
             not properly support DNSSEC all validations will fail.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"allow-downgrade"</literal></term>
-          <listitem><para>
+        - `"allow-downgrade"`:
             DNSSEC validation is attempted, but if the server does not
             support DNSSEC properly, DNSSEC mode is automatically
             disabled. Note that this mode makes DNSSEC validation
@@ -107,22 +82,14 @@ in
             be able to trigger a downgrade to non-DNSSEC mode by
             synthesizing a DNS response that suggests DNSSEC was not
             supported.
-          </para></listitem>
-        </varlistentry>
-        <varlistentry>
-          <term><literal>"false"</literal></term>
-          <listitem><para>
-            DNS lookups are not DNSSEC validated.
-          </para></listitem>
-        </varlistentry>
-        </variablelist>
+        - `"false"`: DNS lookups are not DNSSEC validated.
       '';
     };
 
     services.resolved.extraConfig = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Extra config to append to resolved.conf.
       '';
     };
@@ -178,6 +145,8 @@ in
     # If networkmanager is enabled, ask it to interface with resolved.
     networking.networkmanager.dns = "systemd-resolved";
 
+    networking.resolvconf.package = pkgs.systemd;
+
   };
 
 }
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 317583669809..4596c160a957 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -14,6 +14,8 @@ extraUtils="@extraUtils@"
 export LD_LIBRARY_PATH=@extraUtils@/lib
 export PATH=@extraUtils@/bin
 ln -s @extraUtils@/bin /bin
+# hardcoded in util-linux's mount helper search path `/run/wrappers/bin:/run/current-system/sw/bin:/sbin`
+ln -s @extraUtils@/bin /sbin
 
 # Copy the secrets to their needed location
 if [ -d "@extraUtils@/secrets" ]; then
@@ -318,11 +320,7 @@ checkFS() {
 
     echo "checking $device..."
 
-    fsckFlags=
-    if test "$fsType" != "btrfs"; then
-        fsckFlags="-V -a"
-    fi
-    fsck $fsckFlags "$device"
+    fsck -V -a "$device"
     fsckResult=$?
 
     if test $(($fsckResult | 2)) = $fsckResult; then
@@ -344,6 +342,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() {
@@ -557,6 +563,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
 
@@ -568,7 +577,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/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index d10ebac56828..95dcdfd7fbe1 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -31,6 +31,9 @@ let
   # mounting `/`, like `/` on a loopback).
   fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
 
+  # Determine whether zfs-mount(8) is needed.
+  zfsRequiresMountHelper = any (fs: lib.elem "zfsutil" fs.options) fileSystems;
+
   # A utility for enumerating the shared-library dependencies of a program
   findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" ''
     set -euo pipefail
@@ -107,6 +110,22 @@ let
         copy_bin_and_libs $BIN
       done
 
+      ${optionalString zfsRequiresMountHelper ''
+        # Filesystems using the "zfsutil" option are mounted regardless of the
+        # mount.zfs(8) helper, but it is required to ensure that ZFS properties
+        # are used as mount options.
+        #
+        # BusyBox does not use the ZFS helper in the first place.
+        # util-linux searches /sbin/ as last path for helpers (stage-1-init.sh
+        # must symlink it to the store PATH).
+        # Without helper program, both `mount`s silently fails back to internal
+        # code, using default options and effectively ignore security relevant
+        # ZFS properties such as `setuid=off` and `exec=off` (unless manually
+        # duplicated in `fileSystems.*.options`, defeating "zfsutil"'s purpose).
+        copy_bin_and_libs ${pkgs.util-linux}/bin/mount
+        copy_bin_and_libs ${pkgs.zfs}/bin/mount.zfs
+      ''}
+
       # Copy some util-linux stuff.
       copy_bin_and_libs ${pkgs.util-linux}/sbin/blkid
 
@@ -186,8 +205,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)"
@@ -200,28 +220,33 @@ let
 
       # Strip binaries further than normal.
       chmod -R u+w $out
-      stripDirs "$STRIP" "lib bin" "-s"
+      stripDirs "$STRIP" "$RANLIB" "lib bin" "-s"
 
       # Run patchelf to make the programs refer to the copied libraries.
       find $out/bin $out/lib -type f | while read i; do
-        if ! test -L $i; then
-          nuke-refs -e $out $i
-        fi
+        nuke-refs -e $out $i
       done
 
       find $out/bin -type f | while read i; do
-        if ! test -L $i; then
-          echo "patching $i..."
-          patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
-        fi
+        echo "patching $i..."
+        patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
+      done
+
+      find $out/lib -type f \! -name 'ld*.so.?' | while read i; do
+        echo "patching $i..."
+        patchelf --set-rpath $out/lib $i
       done
 
       if [ -z "${toString (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform)}" ]; then
       # Make sure that the patchelf'ed binaries still work.
       echo "testing patched programs..."
       $out/bin/ash -c 'echo hello world' | grep "hello world"
-      export LD_LIBRARY_PATH=$out/lib
-      $out/bin/mount --help 2>&1 | grep -q "BusyBox"
+      ${if zfsRequiresMountHelper then ''
+        $out/bin/mount -V 1>&1 | grep -q "mount from util-linux"
+        $out/bin/mount.zfs -h 2>&1 | grep -q "Usage: mount.zfs"
+      '' else ''
+        $out/bin/mount --help 2>&1 | grep -q "BusyBox"
+      ''}
       $out/bin/blkid -V 2>&1 | grep -q 'libblkid'
       $out/bin/udevadm --version
       $out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:"
@@ -260,8 +285,6 @@ let
     } ''
       mkdir -p $out
 
-      echo 'ENV{LD_LIBRARY_PATH}="${extraUtils}/lib"' > $out/00-env.rules
-
       cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/
       cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/
       cp -v ${udev}/lib/udev/rules.d/75-net-description.rules $out/
@@ -453,12 +476,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><replaceable>resume_offset</replaceable></literal>.
+        You should also use {var}`boot.kernelParams` to specify
+        `«resume_offset»`.
       '';
     };
 
@@ -466,7 +489,7 @@ in
       type = types.bool;
       default = !config.boot.isContainer;
       defaultText = literalExpression "!config.boot.isContainer";
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the NixOS initial RAM disk (initrd). This may be
         needed to perform some initialisation tasks (like mounting
         network/encrypted file systems) before continuing the boot process.
@@ -480,11 +503,11 @@ in
           options = {
             source = mkOption {
               type = types.package;
-              description = "The object to make available inside the initrd.";
+              description = lib.mdDoc "The object to make available inside the initrd.";
             };
           };
         });
-      description = ''
+      description = lib.mdDoc ''
         Extra files to link and copy in to the initrd.
       '';
     };
@@ -492,7 +515,7 @@ in
     boot.initrd.prepend = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         Other initrd files to prepend to the final initrd we are building.
       '';
     };
@@ -500,15 +523,15 @@ in
     boot.initrd.checkJournalingFS = mkOption {
       default = true;
       type = types.bool;
-      description = ''
-        Whether to run <command>fsck</command> on journaling filesystems such as ext3.
+      description = lib.mdDoc ''
+        Whether to run {command}`fsck` on journaling filesystems such as ext3.
       '';
     };
 
     boot.initrd.preLVMCommands = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed immediately before LVM discovery.
       '';
     };
@@ -516,7 +539,7 @@ in
     boot.initrd.preDeviceCommands = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed before udev is started to create
         device nodes.
       '';
@@ -525,17 +548,17 @@ in
     boot.initrd.postDeviceCommands = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed immediately after stage 1 of the
         boot has loaded kernel modules and created device nodes in
-        <filename>/dev</filename>.
+        {file}`/dev`.
       '';
     };
 
     boot.initrd.postMountCommands = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed immediately after the stage 1
         filesystems have been mounted.
       '';
@@ -544,7 +567,7 @@ in
     boot.initrd.preFailCommands = mkOption {
       default = "";
       type = types.lines;
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed before the failure prompt is shown.
       '';
     };
@@ -553,7 +576,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.
@@ -564,7 +587,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
@@ -576,7 +599,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.
@@ -589,16 +612,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.
       '';
@@ -608,14 +629,14 @@ in
     boot.initrd.compressorArgs = mkOption {
       default = null;
       type = types.nullOr (types.listOf types.str);
-      description = "Arguments to pass to the compressor for the initrd image, or null to use the compressor's defaults.";
+      description = lib.mdDoc "Arguments to pass to the compressor for the initrd image, or null to use the compressor's defaults.";
     };
 
     boot.initrd.secrets = mkOption
       { default = {};
         type = types.attrsOf (types.nullOr types.path);
         description =
-          ''
+          lib.mdDoc ''
             Secrets to append to the initrd. The attribute name is the
             path the secret should have inside the initrd, the value
             is the path it should be copied from (or null for the same
@@ -633,23 +654,21 @@ in
       default = [ ];
       example = [ "btrfs" ];
       type = types.listOf types.str;
-      description = "Names of supported filesystem types in the initial ramdisk.";
+      description = lib.mdDoc "Names of supported filesystem types in the initial ramdisk.";
     };
 
     boot.initrd.verbose = mkOption {
       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" ];`
         '';
     };
 
@@ -658,7 +677,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.
@@ -670,12 +689,12 @@ in
         options.neededForBoot = mkOption {
           default = false;
           type = types.bool;
-          description = ''
+          description = lib.mdDoc ''
             If set, this file system will be mounted in the initial ramdisk.
             Note that the file system will always be mounted in the initial
             ramdisk if its mount point is one of the following:
             ${concatStringsSep ", " (
-              forEach utils.pathsNeededForBoot (i: "<filename>${i}</filename>")
+              forEach utils.pathsNeededForBoot (i: "{file}`${i}`")
             )}.
           '';
         };
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index f2a839d07868..78cc8e8d45a3 100755
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -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/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix
index f6461daf3116..6ed915c339e0 100644
--- a/nixos/modules/system/boot/stage-2.nix
+++ b/nixos/modules/system/boot/stage-2.nix
@@ -10,9 +10,8 @@ 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;
     isExecutable = true;
-    inherit (config.nix) readOnlyStore;
     inherit useHostResolvConf;
     inherit (config.system.build) earlyMountScript;
     path = lib.makeBinPath ([
@@ -37,15 +36,26 @@ in
         default = "";
         example = "rm -f /var/log/messages";
         type = types.lines;
-        description = ''
+        description = lib.mdDoc ''
           Shell commands to be executed just before systemd is started.
         '';
       };
 
+      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;
-        description = ''
+        description = lib.mdDoc ''
           The program to execute to start systemd.
         '';
       };
@@ -53,7 +63,7 @@ in
       extraSystemdUnitPaths = mkOption {
         default = [];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           Additional paths that get appended to the SYSTEMD_UNIT_PATH environment variable
           that can contain mutable unit files.
         '';
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 2c9ee9fc319f..e37ed8531810 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -8,8 +8,6 @@ let
 
   cfg = config.systemd;
 
-  systemd = cfg.package;
-
   inherit (systemdUtils.lib)
     generateUnits
     targetToUnit
@@ -35,11 +33,11 @@ let
       "nss-lookup.target"
       "nss-user-lookup.target"
       "time-sync.target"
-    ] ++ (optionals cfg.package.withCryptsetup [
+    ] ++ optionals cfg.package.withCryptsetup [
       "cryptsetup.target"
       "cryptsetup-pre.target"
       "remote-cryptsetup.target"
-    ]) ++ [
+    ] ++ [
       "sigpwr.target"
       "timers.target"
       "paths.target"
@@ -123,7 +121,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"
@@ -133,20 +131,30 @@ let
 
       # Slices / containers.
       "slices.target"
+    ] ++ optionals cfg.package.withImportd [
+      "systemd-importd.service"
+    ] ++ optionals cfg.package.withMachined [
       "machine.slice"
       "machines.target"
-      "systemd-importd.service"
       "systemd-machined.service"
+    ] ++ [
       "systemd-nspawn@.service"
 
       # Misc.
       "systemd-sysctl.service"
+    ] ++ optionals cfg.package.withTimedated [
       "dbus-org.freedesktop.timedate1.service"
-      "dbus-org.freedesktop.locale1.service"
-      "dbus-org.freedesktop.hostname1.service"
       "systemd-timedated.service"
+    ] ++ optionals cfg.package.withLocaled [
+      "dbus-org.freedesktop.locale1.service"
       "systemd-localed.service"
+    ] ++ 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"
     ] ++ cfg.additionalUpstreamSystemUnits;
@@ -172,11 +180,11 @@ in
       default = pkgs.systemd;
       defaultText = literalExpression "pkgs.systemd";
       type = types.package;
-      description = "The systemd package.";
+      description = lib.mdDoc "The systemd package.";
     };
 
     systemd.units = mkOption {
-      description = "Definition of systemd units.";
+      description = lib.mdDoc "Definition of systemd units.";
       default = {};
       type = systemdUtils.types.units;
     };
@@ -185,43 +193,43 @@ in
       default = [];
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
-      description = "Packages providing systemd units and hooks.";
+      description = lib.mdDoc "Packages providing systemd units and hooks.";
     };
 
     systemd.targets = mkOption {
       default = {};
       type = systemdUtils.types.targets;
-      description = "Definition of systemd target units.";
+      description = lib.mdDoc "Definition of systemd target units.";
     };
 
     systemd.services = mkOption {
       default = {};
       type = systemdUtils.types.services;
-      description = "Definition of systemd service units.";
+      description = lib.mdDoc "Definition of systemd service units.";
     };
 
     systemd.sockets = mkOption {
       default = {};
       type = systemdUtils.types.sockets;
-      description = "Definition of systemd socket units.";
+      description = lib.mdDoc "Definition of systemd socket units.";
     };
 
     systemd.timers = mkOption {
       default = {};
       type = systemdUtils.types.timers;
-      description = "Definition of systemd timer units.";
+      description = lib.mdDoc "Definition of systemd timer units.";
     };
 
     systemd.paths = mkOption {
       default = {};
       type = systemdUtils.types.paths;
-      description = "Definition of systemd path units.";
+      description = lib.mdDoc "Definition of systemd path units.";
     };
 
     systemd.mounts = mkOption {
       default = [];
       type = systemdUtils.types.mounts;
-      description = ''
+      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
         the 'where' attribute.
@@ -231,7 +239,7 @@ in
     systemd.automounts = mkOption {
       default = [];
       type = systemdUtils.types.automounts;
-      description = ''
+      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
         the 'where' attribute.
@@ -241,41 +249,41 @@ in
     systemd.slices = mkOption {
       default = {};
       type = systemdUtils.types.slices;
-      description = "Definition of slice configurations.";
+      description = lib.mdDoc "Definition of slice configurations.";
     };
 
     systemd.generators = mkOption {
       type = types.attrsOf types.path;
       default = {};
       example = { systemd-gpt-auto-generator = "/dev/null"; };
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd generators.
-        For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
-        <literal>/etc/systemd/system-generators/NAME</literal> to <literal>VALUE</literal>.
+        For each `NAME = VALUE` pair of the attrSet, a link is generated from
+        `/etc/systemd/system-generators/NAME` to `VALUE`.
       '';
     };
 
     systemd.shutdown = mkOption {
       type = types.attrsOf types.path;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Definition of systemd shutdown executables.
-        For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
-        <literal>/etc/systemd/system-shutdown/NAME</literal> to <literal>VALUE</literal>.
+        For each `NAME = VALUE` pair of the attrSet, a link is generated from
+        `/etc/systemd/system-shutdown/NAME` to `VALUE`.
       '';
     };
 
     systemd.defaultUnit = mkOption {
       default = "multi-user.target";
       type = types.str;
-      description = "Default unit started when the system boots.";
+      description = lib.mdDoc "Default unit started when the system boots.";
     };
 
     systemd.ctrlAltDelUnit = mkOption {
       default = "reboot.target";
       type = types.str;
       example = "poweroff.target";
-      description = ''
+      description = lib.mdDoc ''
         Target that should be started when Ctrl-Alt-Delete is pressed.
       '';
     };
@@ -284,8 +292,8 @@ in
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { TZ = "CET"; };
-      description = ''
-        Environment variables passed to <emphasis>all</emphasis> systemd units.
+      description = lib.mdDoc ''
+        Environment variables passed to *all* systemd units.
       '';
     };
 
@@ -293,16 +301,16 @@ in
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { SYSTEMD_LOG_LEVEL = "debug"; };
-      description = ''
+      description = lib.mdDoc ''
         Environment variables of PID 1. These variables are
-        <emphasis>not</emphasis> passed to started units.
+        *not* passed to started units.
       '';
     };
 
     systemd.enableCgroupAccounting = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable cgroup accounting.
       '';
     };
@@ -310,7 +318,7 @@ in
     systemd.enableUnifiedCgroupHierarchy = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the unified cgroup hierarchy (cgroupsv2).
       '';
     };
@@ -319,9 +327,9 @@ in
       default = "";
       type = types.lines;
       example = "DefaultLimitCORE=infinity";
-      description = ''
-        Extra config options for systemd. See man systemd-system.conf for
-        available options.
+      description = lib.mdDoc ''
+        Extra config options for systemd. See systemd-system.conf(5) man page
+        for available options.
       '';
     };
 
@@ -329,7 +337,7 @@ in
       default = "";
       type = types.lines;
       example = "HibernateDelaySec=1h";
-      description = ''
+      description = lib.mdDoc ''
         Extra config options for systemd sleep state logic.
         See sleep.conf.d(5) man page for available options.
       '';
@@ -339,7 +347,7 @@ in
       default = [ ];
       type = types.listOf types.str;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
-      description = ''
+      description = lib.mdDoc ''
         Additional units shipped with systemd that shall be enabled.
       '';
     };
@@ -348,10 +356,10 @@ in
       default = [ ];
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
-      description = ''
+      description = lib.mdDoc ''
         A list of units to skip when generating system systemd configuration directory. This has
-        priority over upstream units, <option>systemd.units</option>, and
-        <option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to
+        priority over upstream units, {option}`systemd.units`, and
+        {option}`systemd.additionalUpstreamSystemUnits`. The main purpose of this is to
         prevent a upstream systemd unit from being added to the initrd with any modifications made to it
         by other NixOS modules.
       '';
@@ -361,7 +369,7 @@ in
       type = types.nullOr types.path;
       default = null;
       example = "/dev/watchdog";
-      description = ''
+      description = lib.mdDoc ''
         The path to a hardware watchdog device which will be managed by systemd.
         If not specified, systemd will default to /dev/watchdog.
       '';
@@ -371,7 +379,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "30s";
-      description = ''
+      description = lib.mdDoc ''
         The amount of time which can elapse before a watchdog hardware device
         will automatically reboot the system. Valid time units include "ms",
         "s", "min", "h", "d", and "w".
@@ -382,7 +390,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = ''
+      description = lib.mdDoc ''
         The amount of time which can elapse after a reboot has been triggered
         before a watchdog hardware device will automatically reboot the system.
         Valid time units include "ms", "s", "min", "h", "d", and "w".
@@ -393,7 +401,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = ''
+      description = lib.mdDoc ''
         The amount of time which can elapse when kexec is being executed before
         a watchdog hardware device will automatically reboot the system. This
         option should only be enabled if reloadTime is also enabled. Valid
@@ -432,7 +440,7 @@ in
 
     system.build.units = cfg.units;
 
-    system.nssModules = [ systemd.out ];
+    system.nssModules = [ cfg.package.out ];
     system.nssDatabases = {
       hosts = (mkMerge [
         (mkOrder 400 ["mymachines"]) # 400 to ensure it comes before resolve (which is mkBefore'd)
@@ -446,7 +454,7 @@ in
       ]);
     };
 
-    environment.systemPackages = [ systemd ];
+    environment.systemPackages = [ cfg.package ];
 
     environment.etc = let
       # generate contents for /etc/systemd/system-${type} from attrset of links and packages
@@ -550,7 +558,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
@@ -587,6 +596,12 @@ in
     systemd.services.systemd-importd.environment = proxy_env;
     systemd.services.systemd-pstore.wantedBy = [ "sysinit.target" ]; # see #81138
 
+    # NixOS has kernel modules in a different location, so override that here.
+    systemd.services.kmod-static-nodes.unitConfig.ConditionFileNotEmpty = [
+      ""  # required to unset the previous value!
+      "/run/booted-system/kernel-modules/lib/modules/%v/modules.devname"
+    ];
+
     # Don't bother with certain units in containers.
     systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
     systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
@@ -597,6 +612,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.services.systemd-oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
+
     services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
diff --git a/nixos/modules/system/boot/systemd/coredump.nix b/nixos/modules/system/boot/systemd/coredump.nix
index b6ee2cff1f9a..c2ca973d3807 100644
--- a/nixos/modules/system/boot/systemd/coredump.nix
+++ b/nixos/modules/system/boot/systemd/coredump.nix
@@ -10,9 +10,9 @@ in {
     systemd.coredump.enable = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether core dumps should be processed by
-        <command>systemd-coredump</command>. If disabled, core dumps
+        {command}`systemd-coredump`. If disabled, core dumps
         appear in the current directory of the crashing process.
       '';
     };
@@ -21,37 +21,44 @@ in {
       default = "";
       type = types.lines;
       example = "Storage=journal";
-      description = ''
+      description = lib.mdDoc ''
         Extra config options for systemd-coredump. See coredump.conf(5) man page
         for available options.
       '';
     };
   };
 
-  config = {
-    systemd.additionalUpstreamSystemUnits = [
-      "systemd-coredump.socket"
-      "systemd-coredump@.service"
-    ];
-
-    environment.etc = {
-      "systemd/coredump.conf".text =
-      ''
-        [Coredump]
-        ${cfg.extraConfig}
-      '';
+  config = mkMerge [
 
-      # install provided sysctl snippets
-      "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
-      "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
-    };
+    (mkIf cfg.enable {
+      systemd.additionalUpstreamSystemUnits = [
+        "systemd-coredump.socket"
+        "systemd-coredump@.service"
+      ];
 
-    users.users.systemd-coredump = {
-      uid = config.ids.uids.systemd-coredump;
-      group = "systemd-coredump";
-    };
-    users.groups.systemd-coredump = {};
+      environment.etc = {
+        "systemd/coredump.conf".text =
+        ''
+          [Coredump]
+          ${cfg.extraConfig}
+        '';
+
+        # install provided sysctl snippets
+        "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
+        "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
+      };
+
+      users.users.systemd-coredump = {
+        uid = config.ids.uids.systemd-coredump;
+        group = "systemd-coredump";
+      };
+      users.groups.systemd-coredump = {};
+    })
+
+    (mkIf (!cfg.enable) {
+     boot.kernel.sysctl."kernel.core_pattern" = mkDefault "core";
+    })
+
+  ];
 
-    boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.enable) "core";
-  };
 }
diff --git a/nixos/modules/system/boot/systemd/initrd-secrets.nix b/nixos/modules/system/boot/systemd/initrd-secrets.nix
new file mode 100644
index 000000000000..bc65880719d7
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/initrd-secrets.nix
@@ -0,0 +1,36 @@
+{ config, pkgs, lib, ... }:
+
+{
+  config = lib.mkIf (config.boot.initrd.enable && config.boot.initrd.systemd.enable) {
+    # Copy secrets into the initrd if they cannot be appended
+    boot.initrd.systemd.contents = lib.mkIf (!config.boot.loader.supportsInitrdSecrets)
+      (lib.mapAttrs' (dest: source: lib.nameValuePair "/.initrd-secrets/${dest}" { source = if source == null then dest else source; }) config.boot.initrd.secrets);
+
+    # Copy secrets to their respective locations
+    boot.initrd.systemd.services.initrd-nixos-copy-secrets = lib.mkIf (config.boot.initrd.secrets != {}) {
+      description = "Copy secrets into place";
+      # Run as early as possible
+      wantedBy = [ "sysinit.target" ];
+      before = [ "cryptsetup-pre.target" ];
+      unitConfig.DefaultDependencies = false;
+
+      # We write the secrets to /.initrd-secrets and move them because this allows
+      # secrets to be written to /run. If we put the secret directly to /run and
+      # 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
+          mkdir -p "$(dirname "/$secret")"
+          cp "/.initrd-secrets/$secret" "/$secret"
+        done
+      '';
+
+      unitConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+    };
+    # The script needs this
+    boot.initrd.systemd.extraBin.find = "${pkgs.findutils}/bin/find";
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index fc4bd6ff69b6..31702499b0f1 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -100,10 +100,8 @@ 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";
-      in "${device} /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"; };
@@ -126,6 +124,7 @@ let
   initialRamdisk = pkgs.makeInitrdNG {
     name = "initrd-${kernel-name}";
     inherit (config.boot.initrd) compressor compressorArgs prepend;
+    inherit (cfg) strip;
 
     contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths)
       ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents);
@@ -133,12 +132,15 @@ let
 
 in {
   options.boot.initrd.systemd = {
-    enable = mkEnableOption ''systemd in initrd.
+    enable = mkEnableOption (lib.mdDoc "systemd in initrd") // {
+      description = lib.mdDoc ''
+        Whether to enable 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.
-    '';
+        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.
+      '';
+    };
 
     package = (mkPackageOption pkgs "systemd" {
       default = "systemdStage1";
@@ -147,7 +149,7 @@ in {
     };
 
     contents = mkOption {
-      description = "Set of files that have to be linked into the initrd";
+      description = lib.mdDoc "Set of files that have to be linked into the initrd";
       example = literalExpression ''
         {
           "/etc/hostname".text = "mymachine";
@@ -155,49 +157,32 @@ in {
       '';
       visible = false;
       default = {};
-      type = types.attrsOf (types.submodule ({ config, options, name, ... }: {
-        options = {
-          enable = mkEnableOption "copying of this file to initrd and symlinking it" // { default = true; };
-
-          target = mkOption {
-            type = types.path;
-            description = ''
-              Path of the symlink.
-            '';
-            default = name;
-          };
-
-          text = mkOption {
-            default = null;
-            type = types.nullOr types.lines;
-            description = "Text of the file.";
-          };
-
-          source = mkOption {
-            type = types.path;
-            description = "Path of the source file.";
-          };
-        };
-
-        config = {
-          source = mkIf (config.text != null) (
-            let name' = "initrd-" + baseNameOf name;
-            in mkDerivedConfig options.text (pkgs.writeText name')
-          );
-        };
-      }));
+      type = utils.systemdUtils.types.initrdContents;
     };
 
     storePaths = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Store paths to copy into the initrd as well.
       '';
       type = with types; listOf (oneOf [ singleLineStr package ]);
       default = [];
     };
 
+    strip = mkOption {
+      description = lib.mdDoc ''
+        Whether to completely strip executables and libraries copied to the initramfs.
+
+        Setting this to false may save on the order of 30MiB on the
+        machine building the system (by avoiding a binutils
+        reference), at the cost of ~1MiB of initramfs size. This puts
+        this option firmly in the territory of micro-optimisation.
+      '';
+      type = types.bool;
+      default = true;
+    };
+
     extraBin = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Tools to add to /bin
       '';
       example = literalExpression ''
@@ -210,7 +195,7 @@ in {
     };
 
     suppressedStorePaths = mkOption {
-      description = ''
+      description = lib.mdDoc ''
         Store paths specified in the storePaths option that
         should not be copied.
       '';
@@ -219,9 +204,9 @@ in {
     };
 
     emergencyAccess = mkOption {
-      type = with types; oneOf [ bool singleLineStr ];
+      type = with types; oneOf [ bool (nullOr (passwdEntry str)) ];
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         Set to true for unauthenticated emergency access, and false for
         no emergency access.
 
@@ -235,7 +220,7 @@ in {
       type = types.listOf types.package;
       default = [];
       visible = false;
-      description = ''
+      description = lib.mdDoc ''
         Packages to include in /bin for the stage 1 emergency shell.
       '';
     };
@@ -245,7 +230,7 @@ in {
       type = types.listOf types.str;
       visible = false;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
-      description = ''
+      description = lib.mdDoc ''
         Additional units shipped with systemd that shall be enabled.
       '';
     };
@@ -255,17 +240,17 @@ in {
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
       visible = false;
-      description = ''
+      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</option>, and
-        <option>boot.initrd.systemd.additionalUpstreamUnits</option>. The main purpose of this is to
+        priority over upstream units, {option}`boot.initrd.systemd.units`, and
+        {option}`boot.initrd.systemd.additionalUpstreamUnits`. The main purpose of this is to
         prevent a upstream systemd unit from being added to the initrd with any modifications made to it
         by other NixOS modules.
       '';
     };
 
     units = mkOption {
-      description = "Definition of systemd units.";
+      description = lib.mdDoc "Definition of systemd units.";
       default = {};
       visible = false;
       type = systemdUtils.types.units;
@@ -276,49 +261,49 @@ in {
       visible = false;
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
-      description = "Packages providing systemd units and hooks.";
+      description = lib.mdDoc "Packages providing systemd units and hooks.";
     };
 
     targets = mkOption {
       default = {};
       visible = false;
       type = systemdUtils.types.initrdTargets;
-      description = "Definition of systemd target units.";
+      description = lib.mdDoc "Definition of systemd target units.";
     };
 
     services = mkOption {
       default = {};
       type = systemdUtils.types.initrdServices;
       visible = false;
-      description = "Definition of systemd service units.";
+      description = lib.mdDoc "Definition of systemd service units.";
     };
 
     sockets = mkOption {
       default = {};
       type = systemdUtils.types.initrdSockets;
       visible = false;
-      description = "Definition of systemd socket units.";
+      description = lib.mdDoc "Definition of systemd socket units.";
     };
 
     timers = mkOption {
       default = {};
       type = systemdUtils.types.initrdTimers;
       visible = false;
-      description = "Definition of systemd timer units.";
+      description = lib.mdDoc "Definition of systemd timer units.";
     };
 
     paths = mkOption {
       default = {};
       type = systemdUtils.types.initrdPaths;
       visible = false;
-      description = "Definition of systemd path units.";
+      description = lib.mdDoc "Definition of systemd path units.";
     };
 
     mounts = mkOption {
       default = [];
       type = systemdUtils.types.initrdMounts;
       visible = false;
-      description = ''
+      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
         the 'where' attribute.
@@ -329,7 +314,7 @@ in {
       default = [];
       type = systemdUtils.types.automounts;
       visible = false;
-      description = ''
+      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
         the 'where' attribute.
@@ -340,14 +325,17 @@ in {
       default = {};
       type = systemdUtils.types.slices;
       visible = false;
-      description = "Definition of slice configurations.";
+      description = lib.mdDoc "Definition of slice configurations.";
     };
   };
 
   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 = [
+      "autofs4"           # systemd needs this for some features
+      "tpm-tis" "tpm-crb" # systemd-cryptenroll
+    ];
 
     boot.initrd.systemd = {
       initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages;
@@ -366,9 +354,8 @@ in {
           DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"}
         '';
 
-        "/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;
 
@@ -385,23 +372,32 @@ in {
         '';
         "/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases;
 
+        "/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"
-        "${cfg.package}/lib/systemd/systemd-growfs"
+        (lib.mkIf needGrowfs "${cfg.package}/lib/systemd/systemd-growfs")
         "${cfg.package}/lib/systemd/systemd-hibernate-resume"
         "${cfg.package}/lib/systemd/systemd-journald"
-        "${cfg.package}/lib/systemd/systemd-makefs"
+        (lib.mkIf needMakefs "${cfg.package}/lib/systemd/systemd-makefs")
         "${cfg.package}/lib/systemd/systemd-modules-load"
         "${cfg.package}/lib/systemd/systemd-remount-fs"
         "${cfg.package}/lib/systemd/systemd-shutdown"
         "${cfg.package}/lib/systemd/systemd-sulogin-shell"
         "${cfg.package}/lib/systemd/systemd-sysctl"
 
-        # additional systemd directories
-        "${cfg.package}/lib/systemd/system-generators"
+        # generators
+        "${cfg.package}/lib/systemd/system-generators/systemd-debug-generator"
+        "${cfg.package}/lib/systemd/system-generators/systemd-fstab-generator"
+        "${cfg.package}/lib/systemd/system-generators/systemd-gpt-auto-generator"
+        "${cfg.package}/lib/systemd/system-generators/systemd-hibernate-resume-generator"
+        "${cfg.package}/lib/systemd/system-generators/systemd-run-generator"
 
         # utilities needed by systemd
         "${cfg.package.util-linux}/bin/mount"
@@ -410,6 +406,17 @@ 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"
+
+        # the unwrapped systemd-cryptsetup executable
+        "${cfg.package}/lib/systemd/.systemd-cryptsetup-wrapped"
       ] ++ jobScripts;
 
       targets.initrd.aliases = ["default.target"];
@@ -439,8 +446,11 @@ in {
         mkdir -p $out/etc/systemd/system
         touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service
       '')];
-      services."systemd-makefs@".unitConfig.IgnoreOnIsolate = true;
-      services."systemd-growfs@".unitConfig.IgnoreOnIsolate = true;
+      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"];
 
       services.initrd-nixos-activation = {
         after = [ "initrd-fs.target" ];
@@ -502,6 +512,21 @@ in {
           ''systemctl --no-block switch-root /sysroot "''${NEW_INIT}"''
         ];
       };
+
+      services.panic-on-fail = {
+        wantedBy = ["emergency.target"];
+        unitConfig = {
+          DefaultDependencies = false;
+          ConditionKernelCommandLine = [
+            "|boot.panic_on_fail"
+            "|stage1panic"
+          ];
+        };
+        script = ''
+          echo c > /proc/sysrq-trigger
+        '';
+        serviceConfig.Type = "oneshot";
+      };
     };
 
     boot.kernelParams = lib.mkIf (config.boot.resumeDevice != "") [ "resume=${config.boot.resumeDevice}" ];
diff --git a/nixos/modules/system/boot/systemd/journald.nix b/nixos/modules/system/boot/systemd/journald.nix
index 7e14c8ae4077..773163bbcb81 100644
--- a/nixos/modules/system/boot/systemd/journald.nix
+++ b/nixos/modules/system/boot/systemd/journald.nix
@@ -9,13 +9,13 @@ in {
     services.journald.console = mkOption {
       default = "";
       type = types.str;
-      description = "If non-empty, write log messages to the specified TTY device.";
+      description = lib.mdDoc "If non-empty, write log messages to the specified TTY device.";
     };
 
     services.journald.rateLimitInterval = mkOption {
       default = "30s";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Configures the rate limiting interval that is applied to all
         messages generated on the system. This rate limiting is applied
         per-service, so that two services which log do not interfere with
@@ -23,7 +23,7 @@ in {
         units: s, min, h, ms, us. To turn off any kind of rate limiting,
         set either value to 0.
 
-        See <option>services.journald.rateLimitBurst</option> for important
+        See {option}`services.journald.rateLimitBurst` for important
         considerations when setting this value.
       '';
     };
@@ -31,7 +31,7 @@ in {
     services.journald.rateLimitBurst = mkOption {
       default = 10000;
       type = types.int;
-      description = ''
+      description = lib.mdDoc ''
         Configures the rate limiting burst limit (number of messages per
         interval) that is applied to all messages generated on the system.
         This rate limiting is applied per-service, so that two services
@@ -39,11 +39,11 @@ in {
 
         Note that the effective rate limit is multiplied by a factor derived
         from the available free disk space for the journal as described on
-        <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html">
-        journald.conf(5)</link>.
+        [
+        journald.conf(5)](https://www.freedesktop.org/software/systemd/man/journald.conf.html).
 
         Note that the total amount of logs stored is limited by journald settings
-        such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap.
+        such as `SystemMaxUse`, which defaults to a 4 GB cap.
 
         It is thus recommended to compute what period of time that you will be
         able to store logs for when an application logs at full burst rate.
@@ -56,7 +56,7 @@ in {
       default = "";
       type = types.lines;
       example = "Storage=volatile";
-      description = ''
+      description = lib.mdDoc ''
         Extra config options for systemd-journald. See man journald.conf
         for available options.
       '';
@@ -65,7 +65,7 @@ in {
     services.journald.enableHttpGateway = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable the HTTP gateway to the journal.
       '';
     };
@@ -74,7 +74,7 @@ in {
       default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
       defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to forward log messages to syslog.
       '';
     };
diff --git a/nixos/modules/system/boot/systemd/logind.nix b/nixos/modules/system/boot/systemd/logind.nix
index c1e6cfe61d04..b0c927f19f9d 100644
--- a/nixos/modules/system/boot/systemd/logind.nix
+++ b/nixos/modules/system/boot/systemd/logind.nix
@@ -16,27 +16,24 @@ in
       default = "";
       type = types.lines;
       example = "IdleAction=lock";
-      description = ''
+      description = lib.mdDoc ''
         Extra config options for systemd-logind. See
-        <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html">
-        logind.conf(5)</link> for available options.
+        [
+        logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html) for available options.
       '';
     };
 
     services.logind.killUserProcesses = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      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
-        <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#">
-        systemd.scope(5)</link>), and processes are not killed.
-        </para>
+        [systemd.scope(5)](https://www.freedesktop.org/software/systemd/man/systemd.scope.html#)), and processes are not killed.
 
-        <para>
-        See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link>
+        See [logind.conf(5)](https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=)
         for more details.
       '';
     };
@@ -46,7 +43,7 @@ in
       example = "ignore";
       type = logindHandlerType;
 
-      description = ''
+      description = lib.mdDoc ''
         Specifies what to be done when the laptop lid is closed.
       '';
     };
@@ -56,7 +53,7 @@ in
       example = "suspend";
       type = logindHandlerType;
 
-      description = ''
+      description = lib.mdDoc ''
         Specifies what to be done when the laptop lid is closed
         and another screen is added.
       '';
@@ -68,7 +65,7 @@ in
       example = "ignore";
       type = logindHandlerType;
 
-      description = ''
+      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.
@@ -81,8 +78,13 @@ in
       "systemd-logind.service"
       "autovt@.service"
       "systemd-user-sessions.service"
+    ] ++ optionals config.systemd.package.withImportd [
       "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"
       "user-runtime-dir@.service"
diff --git a/nixos/modules/system/boot/systemd/nspawn.nix b/nixos/modules/system/boot/systemd/nspawn.nix
index bf9995d03cc1..cbc89554c9fd 100644
--- a/nixos/modules/system/boot/systemd/nspawn.nix
+++ b/nixos/modules/system/boot/systemd/nspawn.nix
@@ -16,7 +16,7 @@ let
       "LimitNOFILE" "LimitAS" "LimitNPROC" "LimitMEMLOCK" "LimitLOCKS"
       "LimitSIGPENDING" "LimitMSGQUEUE" "LimitNICE" "LimitRTPRIO" "LimitRTTIME"
       "OOMScoreAdjust" "CPUAffinity" "Hostname" "ResolvConf" "Timezone"
-      "LinkJournal"
+      "LinkJournal" "Ephemeral" "AmbientCapability"
     ])
     (assertValueOneOf "Boot" boolValues)
     (assertValueOneOf "ProcessTwo" boolValues)
@@ -26,11 +26,13 @@ let
   checkFiles = checkUnitConfig "Files" [
     (assertOnlyFields [
       "ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystem"
-      "Overlay" "OverlayReadOnly" "PrivateUsersChown"
+      "Overlay" "OverlayReadOnly" "PrivateUsersChown" "BindUser"
+      "Inaccessible" "PrivateUsersOwnership"
     ])
     (assertValueOneOf "ReadOnly" boolValues)
     (assertValueOneOf "Volatile" (boolValues ++ [ "state" ]))
     (assertValueOneOf "PrivateUsersChown" boolValues)
+    (assertValueOneOf "PrivateUsersOwnership" [ "off" "chown" "map" "auto" ])
   ];
 
   checkNetwork = checkUnitConfig "Network" [
@@ -43,16 +45,17 @@ let
   ];
 
   instanceOptions = {
-    options = sharedOptions // {
+    options =
+    (getAttrs [ "enable" ] sharedOptions)
+    // {
       execConfig = mkOption {
         default = {};
         example = { Parameters = "/bin/sh"; };
         type = types.addCheck (types.attrsOf unitOption) checkExec;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Exec]</literal> section of this unit. See
-          <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Exec]` section of this unit. See
+          {manpage}`systemd.nspawn(5)` for details.
         '';
       };
 
@@ -60,11 +63,10 @@ let
         default = {};
         example = { Bind = [ "/home/alice" ]; };
         type = types.addCheck (types.attrsOf unitOption) checkFiles;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Files]</literal> section of this unit. See
-          <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Files]` section of this unit. See
+          {manpage}`systemd.nspawn(5)` for details.
         '';
       };
 
@@ -72,11 +74,10 @@ let
         default = {};
         example = { Private = false; };
         type = types.addCheck (types.attrsOf unitOption) checkNetwork;
-        description = ''
+        description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
-          <literal>[Network]</literal> section of this unit. See
-          <citerefentry><refentrytitle>systemd.nspawn</refentrytitle>
-          <manvolnum>5</manvolnum></citerefentry> for details.
+          `[Network]` section of this unit. See
+          {manpage}`systemd.nspawn(5)` for details.
         '';
       };
     };
@@ -105,7 +106,7 @@ in {
     systemd.nspawn = mkOption {
       default = {};
       type = with types; attrsOf (submodule instanceOptions);
-      description = "Definition of systemd-nspawn configurations.";
+      description = lib.mdDoc "Definition of systemd-nspawn configurations.";
     };
 
   };
diff --git a/nixos/modules/system/boot/systemd/oomd.nix b/nixos/modules/system/boot/systemd/oomd.nix
new file mode 100644
index 000000000000..fad755e278c7
--- /dev/null
+++ b/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/nixos/modules/system/boot/systemd/shutdown.nix b/nixos/modules/system/boot/systemd/shutdown.nix
index 934269316676..b4b750fa9aaf 100644
--- a/nixos/modules/system/boot/systemd/shutdown.nix
+++ b/nixos/modules/system/boot/systemd/shutdown.nix
@@ -1,32 +1,62 @@
-{ config, lib, ... }: let
+{ config, lib, utils, pkgs, ... }: let
 
-  cfg = config.boot.systemd.shutdown;
+  cfg = config.systemd.shutdownRamfs;
+
+  ramfsContents = let
+    storePaths = map (p: "${p}\n") cfg.storePaths;
+    contents = lib.mapAttrsToList (_: v: "${v.source}\n${v.target}") (lib.filterAttrs (_: v: v.enable) cfg.contents);
+  in pkgs.writeText "shutdown-ramfs-contents" (lib.concatStringsSep "\n" (storePaths ++ contents));
 
 in {
-  options.boot.systemd.shutdown = {
-    enable = lib.mkEnableOption "pivoting back to an initramfs for shutdown" // { default = true; };
+  options.systemd.shutdownRamfs = {
+    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 ''
+        {
+          "/lib/systemd/system-shutdown/zpool-sync-shutdown".source = writeShellScript "zpool" "exec ''${zfs}/bin/zpool sync"
+        }
+      '';
+      type = utils.systemdUtils.types.initrdContents;
+    };
+
+    storePaths = lib.mkOption {
+      description = lib.mdDoc ''
+        Store paths to copy into the shutdown ramfs as well.
+      '';
+      type = lib.types.listOf lib.types.singleLineStr;
+      default = [];
+    };
   };
 
   config = lib.mkIf cfg.enable {
+    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"
-          "/run/current-system/systemd/lib/systemd/systemd-shutdown"
         ];
       };
 
-      serviceConfig.Type = "oneshot";
-      script = ''
-        mkdir -p /run/initramfs
-        if ! mountpoint -q /run/initramfs; then
-          mount -t tmpfs tmpfs /run/initramfs
-        fi
-        cp /run/current-system/systemd/lib/systemd/systemd-shutdown /run/initramfs/shutdown
-      '';
+      serviceConfig = {
+        Type = "oneshot";
+        ProtectSystem = "strict";
+        ReadWritePaths = "/run/initramfs";
+        ExecStart = "${pkgs.makeInitrdNGTool}/bin/make-initrd-ng ${ramfsContents} /run/initramfs";
+      };
     };
   };
 }
diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix
index 97d60e9d6527..32b9b275d358 100644
--- a/nixos/modules/system/boot/systemd/tmpfiles.nix
+++ b/nixos/modules/system/boot/systemd/tmpfiles.nix
@@ -12,10 +12,10 @@ in
       type = types.listOf types.str;
       default = [];
       example = [ "d /tmp 1777 root root 10d" ];
-      description = ''
+      description = lib.mdDoc ''
         Rules for creation, deletion and cleaning of volatile and temporary files
         automatically. See
-        <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        {manpage}`tmpfiles.d(5)`
         for the exact format.
       '';
     };
@@ -25,16 +25,16 @@ in
       default = [];
       example = literalExpression "[ pkgs.lvm2 ]";
       apply = map getLib;
-      description = ''
-        List of packages containing <command>systemd-tmpfiles</command> rules.
+      description = lib.mdDoc ''
+        List of packages containing {command}`systemd-tmpfiles` rules.
 
         All files ending in .conf found in
-        <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename>
+        {file}`«pkg»/lib/tmpfiles.d`
         will be included.
         If this folder does not exist or does not contain any files an error will be returned instead.
 
-        If a <filename>lib</filename> output is available, rules are searched there and only there.
-        If there is no <filename>lib</filename> output it will fall back to <filename>out</filename>
+        If a {file}`lib` output is available, rules are searched there and only there.
+        If there is no {file}`lib` output it will fall back to {file}`out`
         and if that does not exist either, the default output will be used.
       '';
     };
@@ -79,6 +79,7 @@ in
 
         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/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix
index 4951aef95584..46d66fe4e688 100644
--- a/nixos/modules/system/boot/systemd/user.nix
+++ b/nixos/modules/system/boot/systemd/user.nix
@@ -14,6 +14,7 @@ let
     generateUnits
     targetToUnit
     serviceToUnit
+    sliceToUnit
     socketToUnit
     timerToUnit
     pathToUnit;
@@ -44,14 +45,14 @@ in {
       default = "";
       type = types.lines;
       example = "DefaultCPUAccounting=yes";
-      description = ''
+      description = lib.mdDoc ''
         Extra config options for systemd user instances. See man systemd-user.conf for
         available options.
       '';
     };
 
     systemd.user.units = mkOption {
-      description = "Definition of systemd per-user units.";
+      description = lib.mdDoc "Definition of systemd per-user units.";
       default = {};
       type = systemdUtils.types.units;
     };
@@ -59,44 +60,44 @@ in {
     systemd.user.paths = mkOption {
       default = {};
       type = systemdUtils.types.paths;
-      description = "Definition of systemd per-user path units.";
+      description = lib.mdDoc "Definition of systemd per-user path units.";
     };
 
     systemd.user.services = mkOption {
       default = {};
       type = systemdUtils.types.services;
-      description = "Definition of systemd per-user service units.";
+      description = lib.mdDoc "Definition of systemd per-user service units.";
     };
 
     systemd.user.slices = mkOption {
       default = {};
       type = systemdUtils.types.slices;
-      description = "Definition of systemd per-user slice units.";
+      description = lib.mdDoc "Definition of systemd per-user slice units.";
     };
 
     systemd.user.sockets = mkOption {
       default = {};
       type = systemdUtils.types.sockets;
-      description = "Definition of systemd per-user socket units.";
+      description = lib.mdDoc "Definition of systemd per-user socket units.";
     };
 
     systemd.user.targets = mkOption {
       default = {};
       type = systemdUtils.types.targets;
-      description = "Definition of systemd per-user target units.";
+      description = lib.mdDoc "Definition of systemd per-user target units.";
     };
 
     systemd.user.timers = mkOption {
       default = {};
       type = systemdUtils.types.timers;
-      description = "Definition of systemd per-user timer units.";
+      description = lib.mdDoc "Definition of systemd per-user timer units.";
     };
 
     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;
@@ -144,6 +145,10 @@ in {
       { # Ensure that pam_systemd gets included. This is special-cased
         # in systemd to provide XDG_RUNTIME_DIR.
         startSession = true;
+        # Disable pam_mount in systemd-user to prevent it from being called
+        # multiple times during login, because it will prevent pam_mount from
+        # unmounting the previously mounted volumes.
+        pamMount = false;
       };
 
     # Some overrides to upstream units.
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
index 6279957fcd63..a6604802c38c 100644
--- a/nixos/modules/system/boot/timesyncd.nix
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -11,7 +11,7 @@ with lib;
         default = !config.boot.isContainer;
         defaultText = literalExpression "!config.boot.isContainer";
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enables the systemd NTP client daemon.
         '';
       };
@@ -19,7 +19,7 @@ with lib;
         default = config.networking.timeServers;
         defaultText = literalExpression "config.networking.timeServers";
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           The set of NTP servers from which to synchronise.
         '';
       };
@@ -29,10 +29,10 @@ with lib;
         example = ''
           PollIntervalMaxSec=180
         '';
-        description = ''
+        description = lib.mdDoc ''
           Extra config options for systemd-timesyncd. See
-          <link xlink:href="https://www.freedesktop.org/software/systemd/man/timesyncd.conf.html">
-          timesyncd.conf(5)</link> for available options.
+          [
+          timesyncd.conf(5)](https://www.freedesktop.org/software/systemd/man/timesyncd.conf.html) for available options.
         '';
       };
     };
diff --git a/nixos/modules/system/boot/tmp.nix b/nixos/modules/system/boot/tmp.nix
index cf6d19eb5f0e..1f9431710aec 100644
--- a/nixos/modules/system/boot/tmp.nix
+++ b/nixos/modules/system/boot/tmp.nix
@@ -14,23 +14,23 @@ in
     boot.cleanTmpDir = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Whether to delete all files in <filename>/tmp</filename> during boot.
+      description = lib.mdDoc ''
+        Whether to delete all files in {file}`/tmp` during boot.
       '';
     };
 
     boot.tmpOnTmpfs = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-         Whether to mount a tmpfs on <filename>/tmp</filename> during boot.
+      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 = ''
+      description = lib.mdDoc ''
         Size of tmpfs in percentage.
         Percentage is defined by systemd.
       '';
diff --git a/nixos/modules/system/boot/uvesafb.nix b/nixos/modules/system/boot/uvesafb.nix
new file mode 100644
index 000000000000..b10dc42887a1
--- /dev/null
+++ b/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/nixos/modules/system/build.nix b/nixos/modules/system/build.nix
index 58dc3f0d4113..41c0258a5a35 100644
--- a/nixos/modules/system/build.nix
+++ b/nixos/modules/system/build.nix
@@ -7,7 +7,7 @@ in
 
     system.build = mkOption {
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Attribute set of derivations used to set up the system.
       '';
       type = types.submoduleWith {
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index ed552fecec53..cfb9c39458ea 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -82,8 +82,8 @@ in
           "default/useradd".text = "GROUP=100 ...";
         }
       '';
-      description = ''
-        Set of files that have to be linked in <filename>/etc</filename>.
+      description = lib.mdDoc ''
+        Set of files that have to be linked in {file}`/etc`.
       '';
 
       type = with types; attrsOf (submodule (
@@ -93,7 +93,7 @@ in
             enable = mkOption {
               type = types.bool;
               default = true;
-              description = ''
+              description = lib.mdDoc ''
                 Whether this /etc file should be generated.  This
                 option allows specific /etc files to be disabled.
               '';
@@ -101,9 +101,9 @@ in
 
             target = mkOption {
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Name of symlink (relative to
-                <filename>/etc</filename>).  Defaults to the attribute
+                {file}`/etc`).  Defaults to the attribute
                 name.
               '';
             };
@@ -111,20 +111,20 @@ in
             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.";
             };
 
             mode = mkOption {
               type = types.str;
               default = "symlink";
               example = "0600";
-              description = ''
-                If set to something else than <literal>symlink</literal>,
+              description = lib.mdDoc ''
+                If set to something else than `symlink`,
                 the file is copied instead of symlinked, with the given
                 file mode.
               '';
@@ -133,7 +133,7 @@ in
             uid = mkOption {
               default = 0;
               type = types.int;
-              description = ''
+              description = lib.mdDoc ''
                 UID of created file. Only takes effect when the file is
                 copied (that is, the mode is not 'symlink').
                 '';
@@ -142,7 +142,7 @@ in
             gid = mkOption {
               default = 0;
               type = types.int;
-              description = ''
+              description = lib.mdDoc ''
                 GID of created file. Only takes effect when the file is
                 copied (that is, the mode is not 'symlink').
               '';
@@ -151,20 +151,20 @@ in
             user = mkOption {
               default = "+${toString config.uid}";
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 User name of created file.
                 Only takes effect when the file is copied (that is, the mode is not 'symlink').
-                Changing this option takes precedence over <literal>uid</literal>.
+                Changing this option takes precedence over `uid`.
               '';
             };
 
             group = mkOption {
               default = "+${toString config.gid}";
               type = types.str;
-              description = ''
+              description = lib.mdDoc ''
                 Group name of created file.
                 Only takes effect when the file is copied (that is, the mode is not 'symlink').
-                Changing this option takes precedence over <literal>gid</literal>.
+                Changing this option takes precedence over `gid`.
               '';
             };
 
diff --git a/nixos/modules/system/etc/setup-etc.pl b/nixos/modules/system/etc/setup-etc.pl
index be6b2d9ae71e..a048261a3df1 100644
--- a/nixos/modules/system/etc/setup-etc.pl
+++ b/nixos/modules/system/etc/setup-etc.pl
@@ -137,7 +137,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/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index d00dc761d6e3..29e3e313336f 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -13,21 +13,32 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to periodically upgrade NixOS to the latest
           version. If enabled, a systemd timer will run
-          <literal>nixos-rebuild switch --upgrade</literal> once a
+          `nixos-rebuild switch --upgrade` once a
           day.
         '';
       };
 
+      operation = mkOption {
+        type = types.enum ["switch" "boot"];
+        default = "switch";
+        example = "boot";
+        description = lib.mdDoc ''
+          Whether to run
+          `nixos-rebuild switch --upgrade` or run
+          `nixos-rebuild boot --upgrade`
+        '';
+      };
+
       flake = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "github:kloenk/nix";
-        description = ''
+        description = lib.mdDoc ''
           The Flake URI of the NixOS configuration to build.
-          Disables the option <option>system.autoUpgrade.channel</option>.
+          Disables the option {option}`system.autoUpgrade.channel`.
         '';
       };
 
@@ -35,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).
         '';
       };
 
@@ -53,11 +64,11 @@ in {
           "extra-binary-caches"
           "http://my-cache.example.org/"
         ];
-        description = ''
-          Any additional flags passed to <command>nixos-rebuild</command>.
+        description = lib.mdDoc ''
+          Any additional flags passed to {command}`nixos-rebuild`.
 
           If you are using flakes and use a local repo you can add
-          <command>[ "--update-input" "nixpkgs" "--commit-lock-file" ]</command>
+          {command}`[ "--update-input" "nixpkgs" "--commit-lock-file" ]`
           to update nixpkgs.
         '';
       };
@@ -66,24 +77,23 @@ in {
         type = types.str;
         default = "04:40";
         example = "daily";
-        description = ''
+        description = lib.mdDoc ''
           How often or when upgrade occurs. For most desktop and server systems
           a sufficient upgrade frequency is once a day.
 
           The format is described in
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
 
       allowReboot = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Reboot the system into the new generation instead of a switch
           if the new generation uses a different kernel, kernel modules
           or initrd than the booted system.
-          See <option>rebootWindow</option> for configuring the times at which a reboot is allowed.
+          See {option}`rebootWindow` for configuring the times at which a reboot is allowed.
         '';
       };
 
@@ -91,34 +101,33 @@ in {
         default = "0";
         type = types.str;
         example = "45min";
-        description = ''
+        description = lib.mdDoc ''
           Add a randomized delay before each automatic upgrade.
           The delay will be chosen between zero and this value.
           This value must be a time span in the format specified by
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>
+          {manpage}`systemd.time(7)`
         '';
       };
 
       rebootWindow = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Define a lower and upper time value (in HH:MM format) which
           constitute a time window during which reboots are allowed after an upgrade.
-          This option only has an effect when <option>allowReboot</option> is enabled.
-          The default value of <literal>null</literal> means that reboots are allowed at any time.
+          This option only has an effect when {option}`allowReboot` is enabled.
+          The default value of `null` means that reboots are allowed at any time.
         '';
         default = null;
         example = { lower = "01:00"; upper = "05:00"; };
         type = with types; nullOr (submodule {
           options = {
             lower = mkOption {
-              description = "Lower limit of the reboot window";
+              description = lib.mdDoc "Lower limit of the reboot window";
               type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
               example = "01:00";
             };
 
             upper = mkOption {
-              description = "Upper limit of the reboot window";
+              description = lib.mdDoc "Upper limit of the reboot window";
               type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}";
               example = "05:00";
             };
@@ -130,7 +139,7 @@ in {
         default = true;
         type = types.bool;
         example = false;
-        description = ''
+        description = lib.mdDoc ''
           Takes a boolean argument. If true, the time when the service
           unit was last triggered is stored on disk. When the timer is
           activated, the service unit is triggered immediately if it
@@ -190,7 +199,7 @@ in {
         nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
         date     = "${pkgs.coreutils}/bin/date";
         readlink = "${pkgs.coreutils}/bin/readlink";
-        shutdown = "${pkgs.systemd}/bin/shutdown";
+        shutdown = "${config.systemd.package}/bin/shutdown";
         upgradeFlag = optional (cfg.channel == null) "--upgrade";
       in if cfg.allowReboot then ''
         ${nixos-rebuild} boot ${toString (cfg.flags ++ upgradeFlag)}
@@ -223,7 +232,7 @@ in {
         ''}
 
         if [ "''${booted}" = "''${built}" ]; then
-          ${nixos-rebuild} switch ${toString cfg.flags}
+          ${nixos-rebuild} ${cfg.operation} ${toString cfg.flags}
         ${optionalString (cfg.rebootWindow != null) ''
           elif [ "''${do_reboot}" != true ]; then
             echo "Outside of configured reboot window, skipping."
@@ -232,7 +241,7 @@ in {
           ${shutdown} -r +1
         fi
       '' else ''
-        ${nixos-rebuild} switch ${toString (cfg.flags ++ upgradeFlag)}
+        ${nixos-rebuild} ${cfg.operation} ${toString (cfg.flags ++ upgradeFlag)}
       '';
 
       startAt = cfg.dates;
diff --git a/nixos/modules/tasks/bcache.nix b/nixos/modules/tasks/bcache.nix
index 0a13522de11f..408ddc02373f 100644
--- a/nixos/modules/tasks/bcache.nix
+++ b/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/nixos/modules/tasks/cpu-freq.nix b/nixos/modules/tasks/cpu-freq.nix
index f1219c07c501..6869ef8b7915 100644
--- a/nixos/modules/tasks/cpu-freq.nix
+++ b/nixos/modules/tasks/cpu-freq.nix
@@ -18,7 +18,7 @@ in
       type = types.nullOr types.str;
       default = null;
       example = "ondemand";
-      description = ''
+      description = lib.mdDoc ''
         Configure the governor used to regulate the frequency of the
         available CPUs. By default, the kernel configures the
         performance governor, although this may be overwritten in your
@@ -34,7 +34,7 @@ in
         type = types.nullOr types.ints.unsigned;
         default = null;
         example = 2200000;
-        description = ''
+        description = lib.mdDoc ''
           The maximum frequency the CPU will use.  Defaults to the maximum possible.
         '';
       };
@@ -43,7 +43,7 @@ in
         type = types.nullOr types.ints.unsigned;
         default = null;
         example = 800000;
-        description = ''
+        description = lib.mdDoc ''
           The minimum frequency the CPU will use.
         '';
       };
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index 06117d19af46..7837a34b4984 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -16,33 +16,33 @@ let
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
+        description = lib.mdDoc "The block device is backed by an encrypted one, adds this device as a initrd luks entry.";
       };
 
       blkDev = mkOption {
         default = null;
         example = "/dev/sda1";
         type = types.nullOr types.str;
-        description = "Location of the backing encrypted device.";
+        description = lib.mdDoc "Location of the backing encrypted device.";
       };
 
       label = mkOption {
         default = null;
         example = "rootfs";
         type = types.nullOr types.str;
-        description = "Label of the unlocked encrypted device. Set <literal>fileSystems.&lt;name?&gt;.device</literal> to <literal>/dev/mapper/&lt;label&gt;</literal> to mount the unlocked device.";
+        description = lib.mdDoc "Label of the unlocked encrypted device. Set `fileSystems.<name?>.device` to `/dev/mapper/<label>` to mount the unlocked device.";
       };
 
       keyFile = mkOption {
         default = null;
         example = "/mnt-root/root/.swapkey";
         type = types.nullOr types.str;
-        description = ''
+        description = lib.mdDoc ''
           Path to a keyfile used to unlock the backing encrypted
           device. At the time this keyfile is accessed, the
-          <literal>neededForBoot</literal> filesystems (see
-          <literal>fileSystems.&lt;name?&gt;.neededForBoot</literal>)
-          will have been mounted under <literal>/mnt-root</literal>,
+          `neededForBoot` filesystems (see
+          `fileSystems.<name?>.neededForBoot`)
+          will have been mounted under `/mnt-root`,
           so the keyfile path should usually start with "/mnt-root/".
         '';
       };
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index b8afe231dd2e..a093baea6a65 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -33,27 +33,27 @@ let
       mountPoint = mkOption {
         example = "/mnt/usb";
         type = nonEmptyWithoutTrailingSlash;
-        description = "Location of the mounted the file system.";
+        description = lib.mdDoc "Location of the mounted file system.";
       };
 
       device = mkOption {
         default = null;
         example = "/dev/sda";
         type = types.nullOr nonEmptyStr;
-        description = "Location of the device.";
+        description = lib.mdDoc "Location of the device.";
       };
 
       fsType = mkOption {
         default = "auto";
         example = "ext3";
         type = nonEmptyStr;
-        description = "Type of the file system.";
+        description = lib.mdDoc "Type of the file system.";
       };
 
       options = mkOption {
         default = [ "defaults" ];
         example = [ "data=journal" ];
-        description = "Options used to mount the file system.";
+        description = lib.mdDoc "Options used to mount the file system.";
         type = types.listOf nonEmptyStr;
       };
 
@@ -61,13 +61,13 @@ let
         default = [ ];
         example = [ "/persist" ];
         type = types.listOf nonEmptyWithoutTrailingSlash;
-        description = ''
+        description = lib.mdDoc ''
           List of paths that should be mounted before this one. This filesystem's
-          <option>device</option> and <option>mountPoint</option> are always
+          {option}`device` and {option}`mountPoint` are always
           checked and do not need to be included explicitly. If a path is added
           to this list, any other filesystem whose mount point is a parent of
           the path will be mounted before this filesystem. The paths do not need
-          to actually be the <option>mountPoint</option> of some other filesystem.
+          to actually be the {option}`mountPoint` of some other filesystem.
         '';
       };
 
@@ -88,25 +88,25 @@ let
         default = null;
         example = "root-partition";
         type = types.nullOr nonEmptyStr;
-        description = "Label of the device (if any).";
+        description = lib.mdDoc "Label of the device (if any).";
       };
 
       autoFormat = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If the device does not currently contain a filesystem (as
-          determined by <command>blkid</command>, then automatically
+          determined by {command}`blkid`, then automatically
           format it with the filesystem type specified in
-          <option>fsType</option>.  Use with caution.
+          {option}`fsType`.  Use with caution.
         '';
       };
 
       formatOptions = mkOption {
         default = "";
         type = types.str;
-        description = ''
-          If <option>autoFormat</option> option is set specifies
+        description = lib.mdDoc ''
+          If {option}`autoFormat` option is set specifies
           extra options passed to mkfs.
         '';
       };
@@ -114,7 +114,7 @@ let
       autoResize = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           If set, the filesystem is grown to its maximum size before
           being mounted. (This is typically the size of the containing
           partition.) This is currently only supported for ext2/3/4
@@ -125,7 +125,7 @@ let
       noCheck = mkOption {
         default = false;
         type = types.bool;
-        description = "Disable running fsck on this filesystem.";
+        description = lib.mdDoc "Disable running fsck on this filesystem.";
       };
 
     };
@@ -153,6 +153,34 @@ let
       specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}"
     '') mounts);
 
+  makeFstabEntries =
+    let
+      fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "nfs4" "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;
+    in fstabFileSystems: { rootPrefix ? "", excludeChecks ? false, extraOpts ? (fs: []) }: 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 (rootPrefix + fs.mountPoint)
+      + " " + fs.fsType
+      + " " + escape (builtins.concatStringsSep "," (fs.options ++ (extraOpts fs)))
+      + " " + (optionalString (!excludeChecks)
+        ("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";
+      excludeChecks = true;
+      extraOpts = fs:
+        (optional fs.autoResize "x-systemd.growfs")
+        ++ (optional fs.autoFormat "x-systemd.makefs");
+    });
+
 in
 
 {
@@ -175,42 +203,41 @@ 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 {
       default = [ ];
       example = [ "btrfs" ];
       type = types.listOf types.str;
-      description = "Names of supported filesystem types.";
+      description = lib.mdDoc "Names of supported filesystem types.";
     };
 
     boot.specialFileSystems = mkOption {
       default = {};
       type = types.attrsOf (types.submodule coreFileSystemOpts);
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         Special filesystems that are mounted very early during boot.
       '';
     };
@@ -219,7 +246,7 @@ in
       default = "5%";
       example = "32m";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
         for the accepted syntax.
       '';
@@ -229,7 +256,7 @@ in
       default = "50%";
       example = "256m";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
         for the accepted syntax.
       '';
@@ -239,7 +266,7 @@ in
       default = "25%";
       example = "256m";
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
         for the accepted syntax.
       '';
@@ -279,10 +306,6 @@ in
 
     environment.etc.fstab.text =
       let
-        fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" "apfs" ];
-        skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
-        # 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}"
@@ -297,18 +320,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:
@@ -316,6 +328,8 @@ in
         )}
       '';
 
+    boot.initrd.systemd.contents."/etc/fstab".source = initrdFstab;
+
     # Provide a target that pulls in all filesystems.
     systemd.targets.fs =
       { description = "All File Systems";
diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix
index b7ebc37dd5cf..bd85a1f8d1f3 100644
--- a/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/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 = ''
-          List of paths to btrfs filesystems to regularily call <command>btrfs scrub</command> on.
+        description = lib.mdDoc ''
+          List of paths to btrfs filesystems to regularily 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.
@@ -36,14 +36,12 @@ in
         default = "monthly";
         type = types.str;
         example = "weekly";
-        description = ''
+        description = lib.mdDoc ''
           Systemd calendar expression for when to scrub btrfs filesystems.
           The recommended period is a month but could be less
-          (<citerefentry><refentrytitle>btrfs-scrub</refentrytitle>
-          <manvolnum>8</manvolnum></citerefentry>).
+          ({manpage}`btrfs-scrub(8)`).
           See
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>
+          {manpage}`systemd.time(7)`
           for more information on the syntax.
         '';
       };
diff --git a/nixos/modules/tasks/filesystems/ext.nix b/nixos/modules/tasks/filesystems/ext.nix
index 9b61f21643ab..edc0efc55213 100644
--- a/nixos/modules/tasks/filesystems/ext.nix
+++ b/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/nixos/modules/tasks/filesystems/jfs.nix b/nixos/modules/tasks/filesystems/jfs.nix
index 700f05af2bec..6d80c4c657da 100644
--- a/nixos/modules/tasks/filesystems/jfs.nix
+++ b/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/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix
index 38c3920a78ad..8c631f0772db 100644
--- a/nixos/modules/tasks/filesystems/nfs.nix
+++ b/nixos/modules/tasks/filesystems/nfs.nix
@@ -30,9 +30,9 @@ in
       idmapd.settings = mkOption {
         type = format.type;
         default = {};
-        description = ''
+        description = lib.mdDoc ''
           libnfsidmap configuration. Refer to
-          <link xlink:href="https://linux.die.net/man/5/idmapd.conf"/>
+          <https://linux.die.net/man/5/idmapd.conf>
           for details.
         '';
         example = literalExpression ''
@@ -49,7 +49,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = ''
+        description = lib.mdDoc ''
           Extra nfs-utils configuration.
         '';
       };
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index fbfc61177d38..0f14f2b501c2 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -58,6 +58,13 @@ let
   # latter case it makes one last attempt at importing, allowing the system to
   # (eventually) boot even with a degraded pool.
   importLib = {zpoolCmd, awkCmd, cfgZfs}: ''
+    for o in $(cat /proc/cmdline); do
+      case $o in
+        zfs_force|zfs_force=1|zfs_force=y)
+          ZFS_FORCE="-f"
+          ;;
+      esac
+    done
     poolReady() {
       pool="$1"
       state="$("${zpoolCmd}" import 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
@@ -78,6 +85,95 @@ let
     }
   '';
 
+  getPoolFilesystems = pool:
+    filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems;
+
+  getPoolMounts = prefix: pool:
+    let
+      # Remove the "/" suffix because even though most mountpoints
+      # won't have it, the "/" mountpoint will, and we can't have the
+      # trailing slash in "/sysroot/" in stage 1.
+      mountPoint = fs: escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint));
+    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)}";
+
+  createImportService = { pool, systemd, force, prefix ? "" }:
+    nameValuePair "zfs-import-${pool}" {
+      description = "Import ZFS pool \"${pool}\"";
+      # we need systemd-udev-settle to ensure devices are available
+      # In the future, hopefully someone will complete this:
+      # https://github.com/zfsonlinux/zfs/pull/4943
+      requires = [ "systemd-udev-settle.service" ];
+      after = [
+        "systemd-udev-settle.service"
+        "systemd-modules-load.service"
+        "systemd-ask-password-console.service"
+      ];
+      wantedBy = (getPoolMounts prefix pool) ++ [ "local-fs.target" ];
+      before = (getPoolMounts prefix pool) ++ [ "local-fs.target" ];
+      unitConfig = {
+        DefaultDependencies = "no";
+      };
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+      environment.ZFS_FORCE = optionalString force "-f";
+      script = (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
+          ${optionalString (if isBool cfgZfs.requestEncryptionCredentials
+                            then cfgZfs.requestEncryptionCredentials
+                            else cfgZfs.requestEncryptionCredentials != []) ''
+            ${getKeyLocations pool} | while IFS=$'\t' read ds kl ks; do
+              {
+              if [[ "$ks" != unavailable ]]; then
+                continue
+              fi
+              case "$kl" in
+                none )
+                  ;;
+                prompt )
+                  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" \
+                      && success=true \
+                      || tries=$((tries - 1))
+                  done
+                  [[ $success = true ]]
+                  ;;
+                * )
+                  ${cfgZfs.package}/sbin/zfs load-key "$ds"
+                  ;;
+              esac
+              } < /dev/null # To protect while read ds kl in case anything reads stdin
+            done
+          ''}
+          echo "Successfully imported ${pool}"
+        else
+          exit 1
+        fi
+      '';
+    };
+
   zedConf = generators.toKeyValue {
     mkKeyValue = generators.mkKeyValueDefault {
       mkValueString = v:
@@ -106,21 +202,21 @@ in
         type = types.package;
         default = if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs;
         defaultText = literalExpression "if config.boot.zfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs";
-        description = "Configured ZFS userland tools package.";
+        description = lib.mdDoc "Configured ZFS userland tools package.";
       };
 
       enabled = mkOption {
         readOnly = true;
         type = types.bool;
         default = inInitrd || inSystem;
-        defaultText = literalDocBook "<literal>true</literal> if ZFS filesystem support is enabled";
-        description = "True if ZFS filesystem support is enabled";
+        defaultText = literalMD "`true` if ZFS filesystem support is enabled";
+        description = lib.mdDoc "True if ZFS filesystem support is enabled";
       };
 
       enableUnstable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Use the unstable zfs package. This might be an option, if the latest
           kernel is not yet supported by a published release of ZFS. Enabling
           this option will install a development version of ZFS on Linux. The
@@ -130,16 +226,25 @@ 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 = [];
         example = [ "tank" "data" ];
-        description = ''
+        description = lib.mdDoc ''
           Name or GUID of extra ZFS pools that you wish to import during boot.
 
           Usually this is not necessary. Instead, you should set the mountpoint property
-          of ZFS filesystems to <literal>legacy</literal> and add the ZFS filesystems to
-          NixOS's <option>fileSystems</option> option, which makes NixOS automatically
+          of ZFS filesystems to `legacy` and add the ZFS filesystems to
+          NixOS's {option}`fileSystems` option, which makes NixOS automatically
           import the associated pool.
 
           However, in some cases (e.g. if you have many filesystems) it may be preferable
@@ -152,7 +257,7 @@ in
       devNodes = mkOption {
         type = types.path;
         default = "/dev/disk/by-id";
-        description = ''
+        description = lib.mdDoc ''
           Name of directory from which to import ZFS devices.
 
           This should be a path under /dev containing stable names for all devices needed, as
@@ -163,16 +268,16 @@ in
       forceImportRoot = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Forcibly import the ZFS root pool(s) during early boot.
 
           This is enabled by default for backwards compatibility purposes, but it is highly
           recommended to disable this option, as it bypasses some of the safeguards ZFS uses
           to protect your ZFS pools.
 
-          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
           boot because it cannot import the root pool, you should boot with the
-          <literal>zfs_force=1</literal> option as a kernel parameter (e.g. by manually
+          `zfs_force=1` option as a kernel parameter (e.g. by manually
           editing the kernel params in grub during boot). You should only need to do this
           once.
         '';
@@ -181,12 +286,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.
         '';
       };
@@ -195,7 +300,7 @@ in
         type = types.either types.bool (types.listOf types.str);
         default = true;
         example = [ "tank" "data" ];
-        description = ''
+        description = lib.mdDoc ''
           If true on import encryption keys or passwords for all encrypted datasets
           are requested. To only decrypt selected datasets supply a list of dataset
           names instead. For root pools the encryption key can be supplied via both
@@ -208,15 +313,15 @@ in
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable the (OpenSolaris-compatible) ZFS auto-snapshotting service.
-          Note that you must set the <literal>com.sun:auto-snapshot</literal>
-          property to <literal>true</literal> on all datasets which you wish
+          Note that you must set the `com.sun:auto-snapshot`
+          property to `true` on all datasets which you wish
           to auto-snapshot.
 
           You can override a child dataset to use, or not use auto-snapshotting
           by setting its flag with the given interval:
-          <literal>zfs set com.sun:auto-snapshot:weekly=false DATASET</literal>
+          `zfs set com.sun:auto-snapshot:weekly=false DATASET`
         '';
       };
 
@@ -224,14 +329,14 @@ in
         default = "-k -p";
         example = "-k -p --utc";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Flags to pass to the zfs-auto-snapshot command.
 
-          Run <literal>zfs-auto-snapshot</literal> (without any arguments) to
+          Run `zfs-auto-snapshot` (without any arguments) to
           see available flags.
 
           If it's not too inconvenient for snapshots to have timestamps in UTC,
-          it is suggested that you append <literal>--utc</literal> to the list
+          it is suggested that you append `--utc` to the list
           of default options (see example).
 
           Otherwise, snapshot names can cause name conflicts or apparent time
@@ -242,7 +347,7 @@ in
       frequent = mkOption {
         default = 4;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Number of frequent (15-minute) auto-snapshots that you wish to keep.
         '';
       };
@@ -250,7 +355,7 @@ in
       hourly = mkOption {
         default = 24;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Number of hourly auto-snapshots that you wish to keep.
         '';
       };
@@ -258,7 +363,7 @@ in
       daily = mkOption {
         default = 7;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Number of daily auto-snapshots that you wish to keep.
         '';
       };
@@ -266,7 +371,7 @@ in
       weekly = mkOption {
         default = 4;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Number of weekly auto-snapshots that you wish to keep.
         '';
       };
@@ -274,7 +379,7 @@ in
       monthly = mkOption {
         default = 12;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Number of monthly auto-snapshots that you wish to keep.
         '';
       };
@@ -282,7 +387,7 @@ in
 
     services.zfs.trim = {
       enable = mkOption {
-        description = "Whether to enable periodic TRIM on all ZFS pools.";
+        description = lib.mdDoc "Whether to enable periodic TRIM on all ZFS pools.";
         default = true;
         example = false;
         type = types.bool;
@@ -292,28 +397,26 @@ in
         default = "weekly";
         type = types.str;
         example = "daily";
-        description = ''
+        description = lib.mdDoc ''
           How often we run trim. For most desktop and server systems
           a sufficient trimming frequency is once a week.
 
           The format is described in
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
     };
 
     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";
         type = types.str;
         example = "daily";
-        description = ''
+        description = lib.mdDoc ''
           Systemd calendar expression when to scrub ZFS pools. See
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
+          {manpage}`systemd.time(7)`.
         '';
       };
 
@@ -321,7 +424,7 @@ in
         default = [];
         type = types.listOf types.str;
         example = [ "tank" ];
-        description = ''
+        description = lib.mdDoc ''
           List of ZFS pools to periodically scrub. If empty, all pools
           will be scrubbed.
         '';
@@ -332,7 +435,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:
@@ -346,7 +449,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";
       };
@@ -368,11 +471,11 @@ in
             ZED_SCRUB_AFTER_RESILVER = false;
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           ZFS Event Daemon /etc/zfs/zed.d/zed.rc content
 
           See
-          <citerefentry><refentrytitle>zed</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+          {manpage}`zed(8)`
           for details on ZED and the scripts in /etc/zfs/zed.d to find the possible variables
         '';
       };
@@ -400,10 +503,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
@@ -428,14 +539,6 @@ in
           '';
         postDeviceCommands = concatStringsSep "\n" ([''
             ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}"
-
-            for o in $(cat /proc/cmdline); do
-              case $o in
-                zfs_force|zfs_force=1)
-                  ZFS_FORCE="-f"
-                  ;;
-              esac
-            done
           ''] ++ [(importLib {
             # See comments at importLib definition.
             zpoolCmd = "zpool";
@@ -461,11 +564,31 @@ in
                 zfs load-key -a
               ''
               else concatMapStrings (fs: ''
-                zfs load-key ${fs}
+                zfs load-key -- ${escapeShellArg fs}
               '') cfgZfs.requestEncryptionCredentials}
         '') rootPools));
+
+        # Systemd in stage 1
+        systemd = {
+          packages = [cfgZfs.package];
+          services = listToAttrs (map (pool: createImportService {
+            inherit pool;
+            systemd = config.boot.initrd.systemd.package;
+            force = cfgZfs.forceImportRoot;
+            prefix = "/sysroot";
+          }) rootPools);
+          extraBin = {
+            # zpool and zfs are already in thanks to fsPackages
+            awk = "${pkgs.gawk}/bin/awk";
+          };
+        };
       };
 
+      systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/zpool".source = pkgs.writeShellScript "zpool-sync-shutdown" ''
+        exec ${cfgZfs.package}/bin/zpool sync
+      '';
+      systemd.shutdownRamfs.storePaths = ["${cfgZfs.package}/bin/zpool"];
+
       # TODO FIXME See https://github.com/NixOS/nixpkgs/pull/99386#issuecomment-798813567. To not break people's bootloader and as probably not everybody would read release notes that thoroughly add inSystem.
       boot.loader.grub = mkIf (inInitrd || inSystem) {
         zfsSupport = true;
@@ -516,79 +639,11 @@ in
       systemd.packages = [ cfgZfs.package ];
 
       systemd.services = let
-        getPoolFilesystems = pool:
-          filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems;
-
-        getPoolMounts = pool:
-          let
-            mountPoint = fs: escapeSystemdPath fs.mountPoint;
-          in
-            map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);
-
-        createImportService = pool:
-          nameValuePair "zfs-import-${pool}" {
-            description = "Import ZFS pool \"${pool}\"";
-            # we need systemd-udev-settle until https://github.com/zfsonlinux/zfs/pull/4943 is merged
-            requires = [ "systemd-udev-settle.service" ];
-            after = [
-              "systemd-udev-settle.service"
-              "systemd-modules-load.service"
-              "systemd-ask-password-console.service"
-            ];
-            wantedBy = (getPoolMounts pool) ++ [ "local-fs.target" ];
-            before = (getPoolMounts pool) ++ [ "local-fs.target" ];
-            unitConfig = {
-              DefaultDependencies = "no";
-            };
-            serviceConfig = {
-              Type = "oneshot";
-              RemainAfterExit = true;
-            };
-            environment.ZFS_FORCE = optionalString cfgZfs.forceImportAll "-f";
-            script = (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
-                ${optionalString (if isBool cfgZfs.requestEncryptionCredentials
-                                  then cfgZfs.requestEncryptionCredentials
-                                  else cfgZfs.requestEncryptionCredentials != []) ''
-                  ${cfgZfs.package}/sbin/zfs list -rHo name,keylocation ${pool} | while IFS=$'\t' read ds kl; do
-                    {
-                      ${optionalString (!isBool cfgZfs.requestEncryptionCredentials) ''
-                         if ! echo '${concatStringsSep "\n" cfgZfs.requestEncryptionCredentials}' | grep -qFx "$ds"; then
-                           continue
-                         fi
-                       ''}
-                    case "$kl" in
-                      none )
-                        ;;
-                      prompt )
-                        ${config.systemd.package}/bin/systemd-ask-password "Enter key for $ds:" | ${cfgZfs.package}/sbin/zfs load-key "$ds"
-                        ;;
-                      * )
-                        ${cfgZfs.package}/sbin/zfs load-key "$ds"
-                        ;;
-                    esac
-                    } < /dev/null # To protect while read ds kl in case anything reads stdin
-                  done
-                ''}
-                echo "Successfully imported ${pool}"
-              else
-                exit 1
-              fi
-            '';
-          };
+        createImportService' = pool: createImportService {
+          inherit pool;
+          systemd = config.systemd.package;
+          force = cfgZfs.forceImportAll;
+        };
 
         # This forces a sync of any ZFS pools prior to poweroff, even if they're set
         # to sync=disabled.
@@ -614,7 +669,7 @@ in
             wantedBy = [ "zfs.target" ];
           };
 
-      in listToAttrs (map createImportService dataPools ++
+      in listToAttrs (map createImportService' dataPools ++
                       map createSyncService allPools ++
                       map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]);
 
@@ -661,7 +716,7 @@ in
           # expand every pool. Otherwise we want to enumerate
           # just the specifically provided list of pools.
           poolListProvider = if cfgExpandOnBoot == "all"
-            then "$(zpool list -H | awk '{print $1}')"
+            then "$(zpool list -H -o name)"
             else lib.escapeShellArgs cfgExpandOnBoot;
         in
         {
@@ -674,6 +729,8 @@ in
             RemainAfterExit = true;
           };
 
+          path = lib.optionals (cfgExpandOnBoot == "all") [ cfgZfs.package ];
+
           script = ''
             for pool in ${poolListProvider}; do
               systemctl start --no-block "zpool-expand@$pool"
@@ -725,10 +782,10 @@ in
         description = "ZFS pools scrubbing";
         after = [ "zfs-import.target" ];
         serviceConfig = {
-          Type = "oneshot";
+          Type = "simple";
         };
         script = ''
-          ${cfgZfs.package}/bin/zpool scrub ${
+          ${cfgZfs.package}/bin/zpool scrub -w ${
             if cfgScrub.pools != [] then
               (concatStringsSep " " cfgScrub.pools)
             else
diff --git a/nixos/modules/tasks/lvm.nix b/nixos/modules/tasks/lvm.nix
index 4108b482cce5..a14f26c02e48 100644
--- a/nixos/modules/tasks/lvm.nix
+++ b/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 ];
@@ -85,13 +89,15 @@ in {
           systemd.initrdBin = lib.mkIf config.boot.initrd.services.lvm.enable [ pkgs.vdo ];
 
           extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable)''
-            ls ${pkgs.vdo}/bin/ | grep -v adaptLVMVDO | while read BIN; do
+            ls ${pkgs.vdo}/bin/ | while read BIN; do
               copy_bin_and_libs ${pkgs.vdo}/bin/$BIN
             done
+            substituteInPlace $out/bin/vdorecover --replace "${pkgs.bash}/bin/bash" "/bin/sh"
+            substituteInPlace $out/bin/adaptLVMVDO.sh --replace "${pkgs.bash}/bin/bash" "/bin/sh"
           '';
 
           extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable)''
-            ls ${pkgs.vdo}/bin/ | grep -v adaptLVMVDO | while read BIN; do
+            ls ${pkgs.vdo}/bin/ | grep -vE '(adaptLVMVDO|vdorecover)' | while read BIN; do
               $out/bin/$(basename $BIN) --help > /dev/null
             done
           '';
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index b0f160c1dbf9..f44dafc9706a 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -85,12 +85,14 @@ let
         hasDefaultGatewaySet = (cfg.defaultGateway != null && cfg.defaultGateway.address != "")
                             || (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "");
 
-        networkLocalCommands = {
+        needNetworkSetup = cfg.resolvconf.enable || cfg.defaultGateway != null || cfg.defaultGateway6 != null;
+
+        networkLocalCommands = lib.mkIf needNetworkSetup {
           after = [ "network-setup.service" ];
           bindsTo = [ "network-setup.service" ];
         };
 
-        networkSetup =
+        networkSetup = lib.mkIf needNetworkSetup
           { description = "Networking Setup";
 
             after = [ "network-pre.target" "systemd-udevd.service" "systemd-sysctl.service" ];
@@ -219,14 +221,15 @@ let
                     cidr = "${route.address}/${toString route.prefixLength}";
                     via = optionalString (route.via != null) ''via "${route.via}"'';
                     options = concatStrings (mapAttrsToList (name: val: "${name} ${val} ") route.options);
+                    type = toString route.type;
                   in
                   ''
                      echo "${cidr}" >> $state
                      echo -n "adding route ${cidr}... "
-                     if out=$(ip route add "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then
+                     if out=$(ip route add ${type} "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then
                        echo "done"
                      elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
-                       echo "'ip route add "${cidr}" ${options} ${via} dev "${i.name}"' failed: $out"
+                       echo "'ip route add ${type} "${cidr}" ${options} ${via} dev "${i.name}"' failed: $out"
                        exit 1
                      fi
                   ''
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 8654539b6629..b24b29c32d4a 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -43,12 +43,6 @@ in
     } {
       assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
       message = "networking.defaultGateway6.interface is not supported by networkd.";
-    } {
-      assertion = cfg.useDHCP == false;
-      message = ''
-        networking.useDHCP is not supported by networkd.
-        Please use per interface configuration and set the global option to false.
-      '';
     } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
       assertion = !rstp;
       message = "networking.bridges.${n}.rstp is not supported by networkd.";
@@ -65,21 +59,58 @@ in
         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;
-          in optionalAttrs (gateway != [ ]) {
-            routes = override [
-              {
+              makeGateway = gateway: {
                 routeConfig = {
                   Gateway = gateway;
                   GatewayOnLink = false;
                 };
-              }
-            ];
+              };
+          in optionalAttrs (gateway != [ ]) {
+            routes = override (map makeGateway gateway);
           } // optionalAttrs (domains != [ ]) {
             domains = override domains;
           };
       in 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}" = {
@@ -103,7 +134,7 @@ in
               # Most of these route options have not been tested.
               # Please fix or report any mistakes you may find.
               routeConfig =
-                optionalAttrs (route.prefixLength > 0) {
+                optionalAttrs (route.address != null && route.prefixLength != null) {
                   Destination = "${route.address}/${toString route.prefixLength}";
                 } //
                 optionalAttrs (route.options ? fastopen_no_cookie) {
@@ -112,6 +143,9 @@ in
                 optionalAttrs (route.via != null) {
                   Gateway = route.via;
                 } //
+                optionalAttrs (route.type != null) {
+                  Type = route.type;
+                } //
                 optionalAttrs (route.options ? onlink) {
                   GatewayOnLink = true;
                 } //
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index d09e9b99248d..4d47a56ccca3 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -59,7 +59,7 @@ let
     { options = {
         address = mkOption {
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
             IPv${toString v} address of the interface. Leave empty to configure the
             interface using DHCP.
           '';
@@ -67,9 +67,9 @@ let
 
         prefixLength = mkOption {
           type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
-          description = ''
+          description = lib.mdDoc ''
             Subnet mask of the interface, specified as the number of
-            bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
+            bits in the prefix (`${if v == 4 then "24" else "64"}`).
           '';
         };
       };
@@ -79,34 +79,50 @@ let
   { options = {
       address = mkOption {
         type = types.str;
-        description = "IPv${toString v} address of the network.";
+        description = lib.mdDoc "IPv${toString v} address of the network.";
       };
 
       prefixLength = mkOption {
         type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
-        description = ''
+        description = lib.mdDoc ''
           Subnet mask of the network, specified as the number of
-          bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
+          bits in the prefix (`${if v == 4 then "24" else "64"}`).
+        '';
+      };
+
+      type = mkOption {
+        type = types.nullOr (types.enum [
+          "unicast" "local" "broadcast" "multicast"
+        ]);
+        default = null;
+        description = lib.mdDoc ''
+          Type of the route.  See the `Route types` section
+          in the `ip-route(8)` manual page for the details.
+
+          Note that `prohibit`, `blackhole`,
+          `unreachable`, and `throw` cannot
+          be configured per device, so they are not available here. Similarly,
+          `nat` hasn't been supported since kernel 2.6.
         '';
       };
 
       via = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = "IPv${toString v} address of the next hop.";
+        description = lib.mdDoc "IPv${toString v} address of the next hop.";
       };
 
       options = mkOption {
         type = types.attrsOf types.str;
         default = { };
         example = { mtu = "1492"; window = "524288"; };
-        description = ''
-          Other route options. See the symbol <literal>OPTIONS</literal>
-          in the <literal>ip-route(8)</literal> manual page for the details.
-          You may also specify <literal>metric</literal>,
-          <literal>src</literal>, <literal>protocol</literal>,
-          <literal>scope</literal>, <literal>from</literal>
-          and <literal>table</literal>, which are technically
+        description = lib.mdDoc ''
+          Other route options. See the symbol `OPTIONS`
+          in the `ip-route(8)` manual page for the details.
+          You may also specify `metric`,
+          `src`, `protocol`,
+          `scope`, `from`
+          and `table`, which are technically
           not route options, in the sense used in the manual.
         '';
       };
@@ -122,21 +138,21 @@ let
 
       address = mkOption {
         type = types.str;
-        description = "The default gateway address.";
+        description = lib.mdDoc "The default gateway address.";
       };
 
       interface = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "enp0s3";
-        description = "The default gateway interface.";
+        description = lib.mdDoc "The default gateway interface.";
       };
 
       metric = mkOption {
         type = types.nullOr types.int;
         default = null;
         example = 42;
-        description = "The default gateway metric/preference.";
+        description = lib.mdDoc "The default gateway metric/preference.";
       };
 
     };
@@ -149,20 +165,20 @@ let
       name = mkOption {
         example = "eth0";
         type = types.str;
-        description = "Name of the interface.";
+        description = lib.mdDoc "Name of the interface.";
       };
 
       tempAddress = mkOption {
         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:
@@ -173,7 +189,7 @@ let
       useDHCP = mkOption {
         type = types.nullOr types.bool;
         default = null;
-        description = ''
+        description = lib.mdDoc ''
           Whether this interface should be configured with dhcp.
           Null implies the old behavior which depends on whether ip addresses
           are specified or not.
@@ -187,7 +203,7 @@ let
           { address = "192.168.1.1"; prefixLength = 24; }
         ];
         type = with types; listOf (submodule (addrOpts 4));
-        description = ''
+        description = lib.mdDoc ''
           List of IPv4 addresses that will be statically assigned to the interface.
         '';
       };
@@ -199,7 +215,7 @@ let
           { address = "2001:1470:fffd:2098::e006"; prefixLength = 64; }
         ];
         type = with types; listOf (submodule (addrOpts 6));
-        description = ''
+        description = lib.mdDoc ''
           List of IPv6 addresses that will be statically assigned to the interface.
         '';
       };
@@ -211,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.
         '';
       };
@@ -231,7 +251,7 @@ let
           { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
         ];
         type = with types; listOf (submodule (routeOpts 6));
-        description = ''
+        description = lib.mdDoc ''
           List of extra IPv6 static routes that will be assigned to the interface.
         '';
       };
@@ -240,7 +260,7 @@ let
         default = null;
         example = "00:11:22:33:44:55";
         type = types.nullOr (types.str);
-        description = ''
+        description = lib.mdDoc ''
           MAC address of the interface. Leave empty to use the default.
         '';
       };
@@ -249,7 +269,7 @@ let
         default = null;
         example = 9000;
         type = types.nullOr types.int;
-        description = ''
+        description = lib.mdDoc ''
           MTU size for packets leaving the interface. Leave empty to use the default.
         '';
       };
@@ -257,7 +277,7 @@ let
       virtual = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Whether this interface is virtual and should be created by tunctl.
           This is mainly useful for creating bridges between a host and a virtual
           network such as VPN or a virtual machine.
@@ -267,7 +287,7 @@ let
       virtualOwner = mkOption {
         default = "root";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           In case of a virtual device, the user who owns it.
         '';
       };
@@ -276,7 +296,7 @@ let
         default = if hasPrefix "tun" name then "tun" else "tap";
         defaultText = literalExpression ''if hasPrefix "tun" name then "tun" else "tap"'';
         type = with types; enum [ "tun" "tap" ];
-        description = ''
+        description = lib.mdDoc ''
           The type of interface to create.
           The default is TUN for an interface name starting
           with "tun", otherwise TAP.
@@ -286,7 +306,7 @@ let
       proxyARP = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Turn on proxy_arp for this device.
           This is mainly useful for creating pseudo-bridges between a real
           interface and a virtual network such as VPN or a virtual machine for
@@ -305,7 +325,7 @@ let
         enable = mkOption {
           type = types.bool;
           default = false;
-          description = "Whether to enable wol on this interface.";
+          description = lib.mdDoc "Whether to enable wol on this interface.";
         };
       };
     };
@@ -352,20 +372,20 @@ let
     options = {
 
       name = mkOption {
-        description = "Name of the interface";
+        description = lib.mdDoc "Name of the interface";
         example = "eth0";
         type = types.str;
       };
 
       vlan = mkOption {
-        description = "Vlan tag to apply to interface";
+        description = lib.mdDoc "Vlan tag to apply to interface";
         example = 10;
         type = types.nullOr types.int;
         default = null;
       };
 
       type = mkOption {
-        description = "Openvswitch type to assign to interface";
+        description = lib.mdDoc "Openvswitch type to assign to interface";
         example = "internal";
         type = types.nullOr types.str;
         default = null;
@@ -391,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}"
@@ -428,7 +441,7 @@ in
       # reasons (as undocumented feature):
       type = types.strMatching
         "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
-      description = ''
+      description = lib.mdDoc ''
         The name of the machine. Leave it empty if you want to obtain it from a
         DHCP server (if using DHCP). The hostname must be a valid DNS label (see
         RFC 1035 section 2.3.1: "Preferred name syntax", RFC 1123 section 2.1:
@@ -458,11 +471,31 @@ in
           both networking.hostName and networking.domain are set properly.
         '';
       defaultText = literalExpression ''"''${networking.hostName}.''${networking.domain}"'';
-      description = ''
+      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.
       '';
     };
 
@@ -470,17 +503,17 @@ in
       default = null;
       example = "4e98920d";
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         The 32-bit host ID of the machine, formatted as 8 hexadecimal characters.
 
         You should try to make this ID unique among your machines. You can
         generate a random 32-bit ID using the following commands:
 
-        <literal>head -c 8 /etc/machine-id</literal>
+        `head -c 8 /etc/machine-id`
 
         (this derives it from the machine-id that systemd generates) or
 
-        <literal>head -c4 /dev/urandom | od -A none -t x4</literal>
+        `head -c4 /dev/urandom | od -A none -t x4`
 
         The primary use case is to ensure when using ZFS that a pool isn't imported
         accidentally on a wrong machine.
@@ -490,7 +523,7 @@ in
     networking.enableIPv6 = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable support for IPv6.
       '';
     };
@@ -502,7 +535,7 @@ in
         interface = "enp3s0";
       };
       type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
-      description = ''
+      description = lib.mdDoc ''
         The default gateway. It can be left empty if it is auto-detected through DHCP.
         It can be specified as a string or an option set along with a network interface.
       '';
@@ -515,7 +548,7 @@ in
         interface = "enp3s0";
       };
       type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
-      description = ''
+      description = lib.mdDoc ''
         The default ipv6 gateway. It can be left empty if it is auto-detected through DHCP.
         It can be specified as a string or an option set along with a network interface.
       '';
@@ -525,7 +558,7 @@ in
       default = null;
       example = 524288;
       type = types.nullOr types.int;
-      description = ''
+      description = lib.mdDoc ''
         The window size of the default gateway. It limits maximal data bursts that TCP peers
         are allowed to send to us.
       '';
@@ -535,7 +568,7 @@ in
       type = types.listOf types.str;
       default = [];
       example = ["130.161.158.4" "130.161.33.17"];
-      description = ''
+      description = lib.mdDoc ''
         The list of nameservers.  It can be left empty if it is auto-detected through DHCP.
       '';
     };
@@ -544,7 +577,7 @@ in
       default = [];
       example = [ "example.com" "home.arpa" ];
       type = types.listOf types.str;
-      description = ''
+      description = lib.mdDoc ''
         The list of search paths used when resolving domain names.
       '';
     };
@@ -553,7 +586,7 @@ in
       default = null;
       example = "home.arpa";
       type = types.nullOr types.str;
-      description = ''
+      description = lib.mdDoc ''
         The domain.  It can be left empty if it is auto-detected through DHCP.
       '';
     };
@@ -561,9 +594,9 @@ in
     networking.useHostResolvConf = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         In containers, whether to use the
-        <filename>resolv.conf</filename> supplied by the host.
+        {file}`resolv.conf` supplied by the host.
       '';
     };
 
@@ -571,9 +604,9 @@ in
       type = types.lines;
       default = "";
       example = "text=anything; echo You can put $text here.";
-      description = ''
+      description = lib.mdDoc ''
         Shell commands to be executed at the end of the
-        <literal>network-setup</literal> systemd service.  Note that if
+        `network-setup` systemd service.  Note that if
         you are using DHCP to obtain the network configuration,
         interfaces may not be fully configured yet.
       '';
@@ -587,10 +620,14 @@ in
             prefixLength = 25;
           } ];
         };
-      description = ''
+      description = lib.mdDoc ''
         The configuration for each network interface.  If
-        <option>networking.useDHCP</option> is true, then every
+        {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);
     };
@@ -602,7 +639,7 @@ in
           vs1.interfaces = [ { name = "eth2"; } { name = "lo2"; type="internal"; } ];
         };
       description =
-        ''
+        lib.mdDoc ''
           This option allows you to define Open vSwitches that connect
           physical networks together. The value of this option is an
           attribute set. Each attribute specifies a vswitch, with the
@@ -615,7 +652,7 @@ in
         options = {
 
           interfaces = mkOption {
-            description = "The physical network interfaces connected by the vSwitch.";
+            description = lib.mdDoc "The physical network interfaces connected by the vSwitch.";
             type = with types; attrsOf (submodule vswitchInterfaceOpts);
           };
 
@@ -623,8 +660,8 @@ in
             type = types.listOf types.str;
             default = [];
             example = [ "ptcp:6653:[::1]" ];
-            description = ''
-              Specify the controller targets. For the allowed options see <literal>man 8 ovs-vsctl</literal>.
+            description = lib.mdDoc ''
+              Specify the controller targets. For the allowed options see `man 8 ovs-vsctl`.
             '';
           };
 
@@ -634,9 +671,9 @@ in
             example = ''
               actions=normal
             '';
-            description = ''
-              OpenFlow rules to insert into the Open vSwitch. All <literal>openFlowRules</literal> are
-              loaded with <literal>ovs-ofctl</literal> within one atomic operation.
+            description = lib.mdDoc ''
+              OpenFlow rules to insert into the Open vSwitch. All `openFlowRules` are
+              loaded with `ovs-ofctl` within one atomic operation.
             '';
           };
 
@@ -645,7 +682,7 @@ in
             type = types.listOf types.str;
             example = [ "OpenFlow10" "OpenFlow13" "OpenFlow14" ];
             default = [ "OpenFlow13" ];
-            description = ''
+            description = lib.mdDoc ''
               Supported versions to enable on this switch.
             '';
           };
@@ -654,8 +691,8 @@ in
           openFlowVersion = mkOption {
             type = types.str;
             default = "OpenFlow13";
-            description = ''
-              Version of OpenFlow protocol to use when communicating with the switch internally (e.g. with <literal>openFlowRules</literal>).
+            description = lib.mdDoc ''
+              Version of OpenFlow protocol to use when communicating with the switch internally (e.g. with `openFlowRules`).
             '';
           };
 
@@ -666,8 +703,8 @@ in
               set-fail-mode <switch_name> secure
               set Bridge <switch_name> stp_enable=true
             '';
-            description = ''
-              Commands to manipulate the Open vSwitch database. Every line executed with <literal>ovs-vsctl</literal>.
+            description = lib.mdDoc ''
+              Commands to manipulate the Open vSwitch database. Every line executed with `ovs-vsctl`.
               All commands are bundled together with the operations for adding the interfaces
               into one atomic operation.
             '';
@@ -686,7 +723,7 @@ in
           br1.interfaces = [ "eth2" "wlan0" ];
         };
       description =
-        ''
+        lib.mdDoc ''
           This option allows you to define Ethernet bridge devices
           that connect physical networks together.  The value of this
           option is an attribute set.  Each attribute specifies a
@@ -702,13 +739,13 @@ in
             example = [ "eth0" "eth1" ];
             type = types.listOf types.str;
             description =
-              "The physical network interfaces connected by the bridge.";
+              lib.mdDoc "The physical network interfaces connected by the bridge.";
           };
 
           rstp = mkOption {
             default = false;
             type = types.bool;
-            description = "Whether the bridge interface should enable rstp.";
+            description = lib.mdDoc "Whether the bridge interface should enable rstp.";
           };
 
         };
@@ -736,7 +773,7 @@ in
             anotherBond.interfaces = [ "enp4s0f0" "enp4s0f1" "enp5s0f0" "enp5s0f1" ];
           }
         '';
-        description = ''
+        description = lib.mdDoc ''
           This option allows you to define bond devices that aggregate multiple,
           underlying networking interfaces together. The value of this option is
           an attribute set. Each attribute specifies a bond, with the attribute
@@ -750,17 +787,17 @@ in
             interfaces = mkOption {
               example = [ "enp4s0f0" "enp4s0f1" "wlan0" ];
               type = types.listOf types.str;
-              description = "The interfaces to bond together";
+              description = lib.mdDoc "The interfaces to bond together";
             };
 
             driverOptions = mkOption {
               type = types.attrsOf types.str;
               default = {};
               example = literalExpression driverOptionsExample;
-              description = ''
+              description = lib.mdDoc ''
                 Options for the bonding driver.
                 Documentation can be found in
-                <link xlink:href="https://www.kernel.org/doc/Documentation/networking/bonding.txt" />
+                <https://www.kernel.org/doc/Documentation/networking/bonding.txt>
               '';
 
             };
@@ -769,7 +806,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.
@@ -780,7 +817,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
@@ -793,7 +830,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.
@@ -806,7 +843,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.
@@ -828,7 +865,7 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to define macvlan interfaces which should
         be automatically created.
       '';
@@ -838,14 +875,14 @@ in
           interface = mkOption {
             example = "enp4s0";
             type = types.str;
-            description = "The interface the macvlan will transmit packets through.";
+            description = lib.mdDoc "The interface the macvlan will transmit packets through.";
           };
 
           mode = mkOption {
             default = null;
             type = types.nullOr types.str;
             example = "vepa";
-            description = "The mode of the macvlan device.";
+            description = lib.mdDoc "The mode of the macvlan device.";
           };
 
         };
@@ -860,16 +897,15 @@ in
           primary = { port = 9001; local = { address = "192.0.2.1"; dev = "eth0"; }; };
           backup =  { port = 9002; };
         };
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to configure Foo Over UDP and Generic UDP Encapsulation
-        endpoints. See <citerefentry><refentrytitle>ip-fou</refentrytitle>
-        <manvolnum>8</manvolnum></citerefentry> for details.
+        endpoints. See {manpage}`ip-fou(8)` for details.
       '';
       type = with types; attrsOf (submodule {
         options = {
           port = mkOption {
             type = port;
-            description = ''
+            description = lib.mdDoc ''
               Local port of the encapsulation UDP socket.
             '';
           };
@@ -877,8 +913,8 @@ in
           protocol = mkOption {
             type = nullOr (ints.between 1 255);
             default = null;
-            description = ''
-              Protocol number of the encapsulated packets. Specifying <literal>null</literal>
+            description = lib.mdDoc ''
+              Protocol number of the encapsulated packets. Specifying `null`
               (the default) creates a GUE endpoint, specifying a protocol number will create
               a FOU endpoint.
             '';
@@ -889,11 +925,11 @@ in
               options = {
                 address = mkOption {
                   type = types.str;
-                  description = ''
+                  description = lib.mdDoc ''
                     Local address to bind to. The address must be available when the FOU
                     endpoint is created, using the scripted network setup this can be achieved
-                    either by setting <literal>dev</literal> or adding dependency information to
-                    <literal>systemd.services.&lt;name&gt;-fou-encap</literal>; it isn't supported
+                    either by setting `dev` or adding dependency information to
+                    `systemd.services.<name>-fou-encap`; it isn't supported
                     when using networkd.
                   '';
                 };
@@ -902,7 +938,7 @@ in
                   type = nullOr str;
                   default = null;
                   example = "eth0";
-                  description = ''
+                  description = lib.mdDoc ''
                     Network device to bind to.
                   '';
                 };
@@ -910,7 +946,7 @@ in
             });
             default = null;
             example = { address = "203.0.113.22"; };
-            description = ''
+            description = lib.mdDoc ''
               Local address (and optionally device) to bind to using the given port.
             '';
           };
@@ -934,7 +970,7 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to define 6-to-4 interfaces which should be automatically created.
       '';
       type = with types; attrsOf (submodule {
@@ -944,7 +980,7 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "10.0.0.1";
-            description = ''
+            description = lib.mdDoc ''
               The address of the remote endpoint to forward traffic over.
             '';
           };
@@ -953,7 +989,7 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "10.0.0.22";
-            description = ''
+            description = lib.mdDoc ''
               The address of the local endpoint which the remote
               side should send packets to.
             '';
@@ -963,7 +999,7 @@ in
             type = types.nullOr types.int;
             default = null;
             example = 255;
-            description = ''
+            description = lib.mdDoc ''
               The time-to-live of the connection to the remote tunnel endpoint.
             '';
           };
@@ -972,7 +1008,7 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "enp4s0f0";
-            description = ''
+            description = lib.mdDoc ''
               The underlying network device on which the tunnel resides.
             '';
           };
@@ -982,17 +1018,16 @@ in
               options = {
                 type = mkOption {
                   type = enum [ "fou" "gue" ];
-                  description = ''
+                  description = lib.mdDoc ''
                     Selects encapsulation type. See
-                    <citerefentry><refentrytitle>ip-link</refentrytitle>
-                    <manvolnum>8</manvolnum></citerefentry> for details.
+                    {manpage}`ip-link(8)` for details.
                   '';
                 };
 
                 port = mkOption {
                   type = port;
                   example = 9001;
-                  description = ''
+                  description = lib.mdDoc ''
                     Destination port for encapsulated packets.
                   '';
                 };
@@ -1001,7 +1036,7 @@ in
                   type = nullOr types.port;
                   default = null;
                   example = 9002;
-                  description = ''
+                  description = lib.mdDoc ''
                     Source port for encapsulated packets. Will be chosen automatically by
                     the kernel if unset.
                   '';
@@ -1010,7 +1045,7 @@ in
             });
             default = null;
             example = { type = "fou"; port = 9001; };
-            description = ''
+            description = lib.mdDoc ''
               Configures encapsulation in UDP packets.
             '';
           };
@@ -1040,7 +1075,7 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         This option allows you to define Generic Routing Encapsulation (GRE) tunnels.
       '';
       type = with types; attrsOf (submodule {
@@ -1050,7 +1085,7 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "10.0.0.1";
-            description = ''
+            description = lib.mdDoc ''
               The address of the remote endpoint to forward traffic over.
             '';
           };
@@ -1059,7 +1094,7 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "10.0.0.22";
-            description = ''
+            description = lib.mdDoc ''
               The address of the local endpoint which the remote
               side should send packets to.
             '';
@@ -1069,7 +1104,7 @@ in
             type = types.nullOr types.str;
             default = null;
             example = "enp4s0f0";
-            description = ''
+            description = lib.mdDoc ''
               The underlying network device on which the tunnel resides.
             '';
           };
@@ -1078,7 +1113,7 @@ in
             type = types.nullOr types.int;
             default = null;
             example = 255;
-            description = ''
+            description = lib.mdDoc ''
               The time-to-live/hoplimit of the connection to the remote tunnel endpoint.
             '';
           };
@@ -1093,7 +1128,7 @@ in
               tun6 = "ip6gre";
               tap6 = "ip6gretap";
             }.${v};
-            description = ''
+            description = lib.mdDoc ''
               Whether the tunnel routes layer 2 (tap) or layer 3 (tun) traffic.
             '';
           };
@@ -1116,7 +1151,7 @@ in
         }
       '';
       description =
-        ''
+        lib.mdDoc ''
           This option allows you to define vlan devices that tag packets
           on top of a physical interface. The value of this option is an
           attribute set. Each attribute specifies a vlan, with the name
@@ -1130,13 +1165,13 @@ in
           id = mkOption {
             example = 1;
             type = types.int;
-            description = "The vlan identifier";
+            description = lib.mdDoc "The vlan identifier";
           };
 
           interface = mkOption {
             example = "enp4s0";
             type = types.str;
-            description = "The interface the vlan will transmit packets through.";
+            description = lib.mdDoc "The interface the vlan will transmit packets through.";
           };
 
         };
@@ -1168,17 +1203,17 @@ in
         }
       '';
       description =
-        ''
+        lib.mdDoc ''
           Creating multiple WLAN interfaces on top of one physical WLAN device (NIC).
 
           The name of the WLAN interface corresponds to the name of the attribute.
           A NIC is referenced by the persistent device name of the WLAN interface that
-          <literal>udev</literal> assigns to a NIC by default.
+          `udev` assigns to a NIC by default.
           If a NIC supports multiple WLAN interfaces, then the one NIC can be used as
-          <literal>device</literal> for multiple WLAN interfaces.
+          `device` for multiple WLAN interfaces.
           If a NIC is used for creating WLAN interfaces, then the default WLAN interface
-          with a persistent device name form <literal>udev</literal> is not created.
-          A WLAN interface with the persistent name assigned from <literal>udev</literal>
+          with a persistent device name form `udev` is not created.
+          A WLAN interface with the persistent name assigned from `udev`
           would have to be created explicitly.
         '';
 
@@ -1189,14 +1224,14 @@ in
           device = mkOption {
             type = types.str;
             example = "wlp6s0";
-            description = "The name of the underlying hardware WLAN device as assigned by <literal>udev</literal>.";
+            description = lib.mdDoc "The name of the underlying hardware WLAN device as assigned by `udev`.";
           };
 
           type = mkOption {
             type = types.enum [ "managed" "ibss" "monitor" "mesh" "wds" ];
             default = "managed";
             example = "ibss";
-            description = ''
+            description = lib.mdDoc ''
               The type of the WLAN interface.
               The type has to be supported by the underlying hardware of the device.
             '';
@@ -1205,39 +1240,37 @@ in
           meshID = mkOption {
             type = types.nullOr types.str;
             default = null;
-            description = "MeshID of interface with type <literal>mesh</literal>.";
+            description = lib.mdDoc "MeshID of interface with type `mesh`.";
           };
 
           flags = mkOption {
             type = with types; nullOr (enum [ "none" "fcsfail" "control" "otherbss" "cook" "active" ]);
             default = null;
             example = "control";
-            description = ''
-              Flags for interface of type <literal>monitor</literal>.
+            description = lib.mdDoc ''
+              Flags for interface of type `monitor`.
             '';
           };
 
           fourAddr = mkOption {
             type = types.nullOr types.bool;
             default = null;
-            description = "Whether to enable <literal>4-address mode</literal> with type <literal>managed</literal>.";
+            description = lib.mdDoc "Whether to enable `4-address mode` with type `managed`.";
           };
 
           mac = mkOption {
             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
             '';
           };
 
@@ -1250,22 +1283,17 @@ in
     networking.useDHCP = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to use DHCP to obtain an IP address and other
         configuration for all network interfaces that are not manually
         configured.
-
-        Using this option is highly discouraged and also incompatible with
-        <option>networking.useNetworkd</option>. Please use
-        <option>networking.interfaces.&lt;name&gt;.useDHCP</option> instead
-        and set this to false.
       '';
     };
 
     networking.useNetworkd = mkOption {
       default = false;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Whether we should use networkd as the network configuration backend or
         the legacy script based system. Note that this option is experimental,
         enable at your own risk.
@@ -1278,10 +1306,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:
@@ -1348,13 +1376,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 = {
@@ -1383,9 +1411,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) ''
@@ -1466,7 +1495,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/nixos/modules/tasks/powertop.nix b/nixos/modules/tasks/powertop.nix
index e8064f9fa80c..3839b7a4260e 100644
--- a/nixos/modules/tasks/powertop.nix
+++ b/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/nixos/modules/tasks/scsi-link-power-management.nix b/nixos/modules/tasks/scsi-link-power-management.nix
index a9d987780ee1..a5395657e992 100644
--- a/nixos/modules/tasks/scsi-link-power-management.nix
+++ b/nixos/modules/tasks/scsi-link-power-management.nix
@@ -25,10 +25,10 @@ in
     powerManagement.scsiLinkPolicy = mkOption {
       default = null;
       type = types.nullOr (types.enum allowedValues);
-      description = ''
+      description = lib.mdDoc ''
         SCSI link power management policy. The kernel default is
         "max_performance".
-        </para><para>
+
         "med_power_with_dipm" is supported by kernel versions
         4.15 and newer.
       '';
diff --git a/nixos/modules/tasks/snapraid.nix b/nixos/modules/tasks/snapraid.nix
index c8dde5b48993..243d25f88423 100644
--- a/nixos/modules/tasks/snapraid.nix
+++ b/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 = {
@@ -14,7 +14,7 @@ in
         d2 = "/mnt/disk2/";
         d3 = "/mnt/disk3/";
       };
-      description = "SnapRAID data disks.";
+      description = lib.mdDoc "SnapRAID data disks.";
       type = attrsOf str;
     };
     parityFiles = mkOption {
@@ -27,7 +27,7 @@ in
         "/mnt/diskt/snapraid.5-parity"
         "/mnt/disku/snapraid.6-parity"
       ];
-      description = "SnapRAID parity files.";
+      description = lib.mdDoc "SnapRAID parity files.";
       type = listOf str;
     };
     contentFiles = mkOption {
@@ -37,46 +37,46 @@ in
         "/mnt/disk1/snapraid.content"
         "/mnt/disk2/snapraid.content"
       ];
-      description = "SnapRAID content list files.";
+      description = lib.mdDoc "SnapRAID content list files.";
       type = listOf str;
     };
     exclude = mkOption {
       default = [ ];
       example = [ "*.unrecoverable" "/tmp/" "/lost+found/" ];
-      description = "SnapRAID exclude directives.";
+      description = lib.mdDoc "SnapRAID exclude directives.";
       type = listOf str;
     };
     touchBeforeSync = mkOption {
       default = true;
       example = false;
-      description =
-        "Whether <command>snapraid touch</command> should be run before <command>snapraid sync</command>.";
+      description = lib.mdDoc
+        "Whether {command}`snapraid touch` should be run before {command}`snapraid sync`.";
       type = bool;
     };
     sync.interval = mkOption {
       default = "01:00";
       example = "daily";
-      description = "How often to run <command>snapraid sync</command>.";
+      description = lib.mdDoc "How often to run {command}`snapraid sync`.";
       type = str;
     };
     scrub = {
       interval = mkOption {
         default = "Mon *-*-* 02:00:00";
         example = "weekly";
-        description = "How often to run <command>snapraid scrub</command>.";
+        description = lib.mdDoc "How often to run {command}`snapraid scrub`.";
         type = str;
       };
       plan = mkOption {
         default = 8;
         example = 5;
-        description =
-          "Percent of the array that should be checked by <command>snapraid scrub</command>.";
+        description = lib.mdDoc
+          "Percent of the array that should be checked by {command}`snapraid scrub`.";
         type = int;
       };
       olderThan = mkOption {
         default = 10;
         example = 20;
-        description =
+        description = lib.mdDoc
           "Number of days since data was last scrubbed before it can be scrubbed again.";
         type = int;
       };
@@ -90,7 +90,7 @@ in
         autosave 500
         pool /pool
       '';
-      description = "Extra config options for SnapRAID.";
+      description = lib.mdDoc "Extra config options for SnapRAID.";
       type = lines;
     };
   };
diff --git a/nixos/modules/tasks/stratis.nix b/nixos/modules/tasks/stratis.nix
new file mode 100644
index 000000000000..9a85fe23f248
--- /dev/null
+++ b/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/nixos/modules/tasks/swraid.nix b/nixos/modules/tasks/swraid.nix
index 0b53a6d152d0..7832bbf92016 100644
--- a/nixos/modules/tasks/swraid.nix
+++ b/nixos/modules/tasks/swraid.nix
@@ -5,12 +5,12 @@
 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
     };
 
     mdadmConf = lib.mkOption {
-      description = "Contents of <filename>/etc/mdadm.conf</filename> in initrd.";
+      description = lib.mdDoc "Contents of {file}`/etc/mdadm.conf` in initrd.";
       type = lib.types.lines;
       default = "";
     };
diff --git a/nixos/modules/tasks/trackpoint.nix b/nixos/modules/tasks/trackpoint.nix
index 029d8a002955..d197a0feb337 100644
--- a/nixos/modules/tasks/trackpoint.nix
+++ b/nixos/modules/tasks/trackpoint.nix
@@ -12,7 +12,7 @@ with lib;
       enable = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable sensitivity and speed configuration for trackpoints.
         '';
       };
@@ -21,7 +21,7 @@ with lib;
         default = 128;
         example = 255;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Configure the trackpoint sensitivity. By default, the kernel
           configures 128.
         '';
@@ -31,7 +31,7 @@ with lib;
         default = 97;
         example = 255;
         type = types.int;
-        description = ''
+        description = lib.mdDoc ''
           Configure the trackpoint speed. By default, the kernel
           configures 97.
         '';
@@ -40,7 +40,7 @@ with lib;
       emulateWheel = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Enable scrolling while holding the middle mouse button.
         '';
       };
@@ -48,7 +48,7 @@ with lib;
       fakeButtons = mkOption {
         default = false;
         type = types.bool;
-        description = ''
+        description = lib.mdDoc ''
           Switch to "bare" PS/2 mouse support in case Trackpoint buttons are not recognized
           properly. This can happen for example on models like the L430, T450, T450s, on
           which the Trackpoint buttons are actually a part of the Synaptics touchpad.
@@ -58,7 +58,7 @@ with lib;
       device = mkOption {
         default = "TPPS/2 IBM TrackPoint";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           The device name of the trackpoint. You can check with xinput.
           Some newer devices (example x1c6) use "TPPS/2 Elan TrackPoint".
         '';
diff --git a/nixos/modules/testing/service-runner.nix b/nixos/modules/testing/service-runner.nix
index 9060be3cca11..bdb35f128a73 100644
--- a/nixos/modules/testing/service-runner.nix
+++ b/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/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index 01447e6ada87..4ab2578eb81e 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -65,33 +65,26 @@ in
       };
     };
 
-    boot.initrd.preDeviceCommands =
-      ''
-        echo 600 > /proc/sys/kernel/hung_task_timeout_secs
-      '';
-
-    boot.initrd.postDeviceCommands =
-      ''
-        # Using acpi_pm as a clock source causes the guest clock to
-        # slow down under high host load.  This is usually a bad
-        # thing, but for VM tests it should provide a bit more
-        # determinism (e.g. if the VM runs at lower speed, then
-        # timeouts in the VM should also be delayed).
-        echo acpi_pm > /sys/devices/system/clocksource/clocksource0/current_clocksource
-      '';
-
-    boot.postBootCommands =
-      ''
-        # Panic on out-of-memory conditions rather than letting the
-        # OOM killer randomly get rid of processes, since this leads
-        # to failures that are hard to diagnose.
-        echo 2 > /proc/sys/vm/panic_on_oom
-      '';
+    boot.kernel.sysctl = {
+      "kernel.hung_task_timeout_secs" = 600;
+      # Panic on out-of-memory conditions rather than letting the
+      # OOM killer randomly get rid of processes, since this leads
+      # to failures that are hard to diagnose.
+      "vm.panic_on_oom" = lib.mkDefault 2;
+    };
 
-    # Panic if an error occurs in stage 1 (rather than waiting for
-    # user intervention).
-    boot.kernelParams =
-      [ "console=${qemu-common.qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ];
+    boot.kernelParams = [
+      "console=${qemu-common.qemuSerialDevice}"
+      # Panic if an error occurs in stage 1 (rather than waiting for
+      # user intervention).
+      "panic=1" "boot.panic_on_fail"
+      # Using acpi_pm as a clock source causes the guest clock to
+      # slow down under high host load.  This is usually a bad
+      # thing, but for VM tests it should provide a bit more
+      # determinism (e.g. if the VM runs at lower speed, then
+      # timeouts in the VM should also be delayed).
+      "clock=acpi_pm"
+    ];
 
     # `xwininfo' is used by the test driver to query open windows.
     environment.systemPackages = [ pkgs.xorg.xwininfo ];
@@ -136,6 +129,9 @@ in
     # Make sure we use the Guest Agent from the QEMU package for testing
     # to reduce the closure size required for the tests.
     services.qemuGuest.package = pkgs.qemu_test.ga;
+
+    # Squelch warning about unset system.stateVersion
+    system.stateVersion = lib.mkDefault lib.trivial.release;
   };
 
 }
diff --git a/nixos/modules/virtualisation/amazon-ec2-amis.nix b/nixos/modules/virtualisation/amazon-ec2-amis.nix
index 91b5237e3371..446a1b0ecaab 100644
--- a/nixos/modules/virtualisation/amazon-ec2-amis.nix
+++ b/nixos/modules/virtualisation/amazon-ec2-amis.nix
@@ -440,5 +440,101 @@ let self = {
   "21.11".ap-east-1.aarch64-linux.hvm-ebs = "ami-0aa3b50a4f2822a00";
   "21.11".sa-east-1.aarch64-linux.hvm-ebs = "ami-00f68eff453d3fe69";
 
-  latest = self."21.11";
+  # 22.05.342.a634c8f6c1f
+
+  "22.05".eu-west-1.x86_64-linux.hvm-ebs = "ami-00badba5cfa0a0c0d";
+  "22.05".af-south-1.x86_64-linux.hvm-ebs = "ami-0d3a6166c1ea4d7b4";
+  "22.05".ap-east-1.x86_64-linux.hvm-ebs = "ami-06445325c360470d8";
+  "22.05".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-009c422293bcf3721";
+  "22.05".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0bfc0397525a67ed8";
+  "22.05".ap-northeast-3.x86_64-linux.hvm-ebs = "ami-0a1fb4d4e08a6065e";
+  "22.05".ap-south-1.x86_64-linux.hvm-ebs = "ami-07ad258dcc69239d2";
+  "22.05".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0f59f7f33cba8b1a4";
+  "22.05".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0d1e49fe30aec165d";
+  "22.05".ap-southeast-3.x86_64-linux.hvm-ebs = "ami-0f5cb24a1e3fc62dd";
+  "22.05".ca-central-1.x86_64-linux.hvm-ebs = "ami-0551a595ba7916462";
+  "22.05".eu-central-1.x86_64-linux.hvm-ebs = "ami-0702eee2e75d541d1";
+  "22.05".eu-north-1.x86_64-linux.hvm-ebs = "ami-0fc6838942cb7d9cb";
+  "22.05".eu-south-1.x86_64-linux.hvm-ebs = "ami-0df9463b8965cdb80";
+  "22.05".eu-west-2.x86_64-linux.hvm-ebs = "ami-08f3c1eb533a42ac1";
+  "22.05".eu-west-3.x86_64-linux.hvm-ebs = "ami-04b50c79dc4009c97";
+  "22.05".me-south-1.x86_64-linux.hvm-ebs = "ami-05c52087afab7024d";
+  "22.05".sa-east-1.x86_64-linux.hvm-ebs = "ami-0732aa0f0c28f281b";
+  "22.05".us-east-1.x86_64-linux.hvm-ebs = "ami-0223db08811f6fb2d";
+  "22.05".us-east-2.x86_64-linux.hvm-ebs = "ami-0a743534fa3e51b41";
+  "22.05".us-west-1.x86_64-linux.hvm-ebs = "ami-0d72ab697beab5ea5";
+  "22.05".us-west-2.x86_64-linux.hvm-ebs = "ami-034946f0c47088751";
+
+  "22.05".eu-west-1.aarch64-linux.hvm-ebs = "ami-08114069426233360";
+  "22.05".af-south-1.aarch64-linux.hvm-ebs = "ami-0a9b83913abd61694";
+  "22.05".ap-east-1.aarch64-linux.hvm-ebs = "ami-03966ad4547f532b7";
+  "22.05".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-0eb7e152c8d5aae7d";
+  "22.05".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-08369e00c5528762b";
+  "22.05".ap-northeast-3.aarch64-linux.hvm-ebs = "ami-0fa14b8d48cdd57c3";
+  "22.05".ap-south-1.aarch64-linux.hvm-ebs = "ami-0f2ca3b542ff0913b";
+  "22.05".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-087def0511ef2687d";
+  "22.05".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-0aa90985199011f04";
+  "22.05".ap-southeast-3.aarch64-linux.hvm-ebs = "ami-0c86c52790deefa23";
+  "22.05".ca-central-1.aarch64-linux.hvm-ebs = "ami-06e932cc9c20403e4";
+  "22.05".eu-central-1.aarch64-linux.hvm-ebs = "ami-07680df1026a9b54c";
+  "22.05".eu-north-1.aarch64-linux.hvm-ebs = "ami-0cbe9f2725e4de706";
+  "22.05".eu-south-1.aarch64-linux.hvm-ebs = "ami-01a83c3892925765f";
+  "22.05".eu-west-2.aarch64-linux.hvm-ebs = "ami-049024d086d039b54";
+  "22.05".eu-west-3.aarch64-linux.hvm-ebs = "ami-0c0ebe20ebfc635a1";
+  "22.05".me-south-1.aarch64-linux.hvm-ebs = "ami-0d662fcaac553e945";
+  "22.05".sa-east-1.aarch64-linux.hvm-ebs = "ami-0888c8f703e00fdb8";
+  "22.05".us-east-1.aarch64-linux.hvm-ebs = "ami-03536a13324333073";
+  "22.05".us-east-2.aarch64-linux.hvm-ebs = "ami-067611519fa817aaa";
+  "22.05".us-west-1.aarch64-linux.hvm-ebs = "ami-0f96be48071c13ab2";
+  "22.05".us-west-2.aarch64-linux.hvm-ebs = "ami-084bc5d777585adfb";
+
+  # 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";
+
+  latest = self."22.11";
 }; in self
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index 9a56b6950155..9751f5755f96 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/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
 
 {
@@ -31,20 +26,12 @@ 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.15";
-        message = "ENA driver fails to build with kernel >= 5.15";
+      { assertion = versionOlder config.boot.kernelPackages.kernel.version "5.17";
+        message = "ENA driver fails to build with kernel >= 5.17";
       }
     ];
 
-    boot.kernelPackages = pkgs.linuxKernel.packages.linux_5_10;
-
-    boot.growPartition = cfg.hvm;
+    boot.growPartition = true;
 
     fileSystems."/" = mkIf (!cfg.zfs.enable) {
       device = "/dev/disk/by-label/nixos";
@@ -66,9 +53,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.
@@ -76,10 +63,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;
@@ -89,67 +73,14 @@ 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.
@@ -168,8 +99,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/nixos/modules/virtualisation/amazon-init.nix b/nixos/modules/virtualisation/amazon-init.nix
index 4f2f8df90eb8..8b98f2e32dd5 100644
--- a/nixos/modules/virtualisation/amazon-init.nix
+++ b/nixos/modules/virtualisation/amazon-init.nix
@@ -11,7 +11,7 @@ let
     echo "attempting to fetch configuration from EC2 user data..."
 
     export HOME=/root
-    export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.git pkgs.gnutar pkgs.gzip pkgs.gnused pkgs.xz config.system.build.nixos-rebuild]}:$PATH
+    export PATH=${pkgs.lib.makeBinPath [ config.nix.package config.systemd.package pkgs.gnugrep pkgs.git pkgs.gnutar pkgs.gzip pkgs.gnused pkgs.xz config.system.build.nixos-rebuild]}:$PATH
     export NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
 
     userData=/etc/ec2-metadata/user-data
@@ -60,7 +60,7 @@ in {
     enable = mkOption {
       default = true;
       type = types.bool;
-      description = ''
+      description = lib.mdDoc ''
         Enable or disable the amazon-init service.
       '';
     };
diff --git a/nixos/modules/virtualisation/amazon-options.nix b/nixos/modules/virtualisation/amazon-options.nix
index 0465571ca926..915bbf9763db 100644
--- a/nixos/modules/virtualisation/amazon-options.nix
+++ b/nixos/modules/virtualisation/amazon-options.nix
@@ -2,19 +2,22 @@
 let
   inherit (lib) literalExpression types;
 in {
+  imports = [
+    (lib.mkRemovedOptionModule [ "ec2" "hvm" ] "Only HVM instances are supported, so specifying it is no longer necessary.")
+  ];
   options = {
     ec2 = {
       zfs = {
         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
@@ -27,13 +30,13 @@ in {
           type = types.attrsOf (types.submodule {
             options = {
               mount = lib.mkOption {
-                description = "Where to mount this dataset.";
+                description = lib.mdDoc "Where to mount this dataset.";
                 type = types.nullOr types.string;
                 default = null;
               };
 
               properties = lib.mkOption {
-                description = "Properties to set on this dataset.";
+                description = lib.mdDoc "Properties to set on this dataset.";
                 type = types.attrsOf types.string;
                 default = {};
               };
@@ -41,18 +44,11 @@ in {
           });
         };
       };
-      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.
         '';
       };
diff --git a/nixos/modules/virtualisation/anbox.nix b/nixos/modules/virtualisation/anbox.nix
index a4da62eb5f79..c7e9e23c4c92 100644
--- a/nixos/modules/virtualisation/anbox.nix
+++ b/nixos/modules/virtualisation/anbox.nix
@@ -10,7 +10,7 @@ let
     address = mkOption {
       default = addr;
       type = types.str;
-      description = ''
+      description = lib.mdDoc ''
         IPv${toString v} ${name} address.
       '';
     };
@@ -18,9 +18,9 @@ let
     prefixLength = mkOption {
       default = pref;
       type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
-      description = ''
+      description = lib.mdDoc ''
         Subnet mask of the ${name} address, specified as the number of
-        bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
+        bits in the prefix (`${if v == 4 then "24" else "64"}`).
       '';
     };
   };
@@ -31,13 +31,13 @@ in
 
   options.virtualisation.anbox = {
 
-    enable = mkEnableOption "Anbox";
+    enable = mkEnableOption (lib.mdDoc "Anbox");
 
     image = mkOption {
       default = pkgs.anbox.image;
       defaultText = literalExpression "pkgs.anbox.image";
       type = types.package;
-      description = ''
+      description = lib.mdDoc ''
         Base android image for Anbox.
       '';
     };
@@ -45,7 +45,7 @@ in
     extraInit = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra shell commands to be run inside the container image during init.
       '';
     };
@@ -57,7 +57,7 @@ in
       dns = mkOption {
         default = "1.1.1.1";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Container DNS server.
         '';
       };
@@ -73,9 +73,6 @@ in
 
     environment.systemPackages = with pkgs; [ anbox ];
 
-    boot.kernelModules = [ "ashmem_linux" "binder_linux" ];
-    boot.extraModulePackages = [ kernelPackages.anbox ];
-
     services.udev.extraRules = ''
       KERNEL=="ashmem", NAME="%k", MODE="0666"
       KERNEL=="binder*", NAME="%k", MODE="0666"
diff --git a/nixos/modules/virtualisation/appvm.nix b/nixos/modules/virtualisation/appvm.nix
new file mode 100644
index 000000000000..9fe2995d37a0
--- /dev/null
+++ b/nixos/modules/virtualisation/appvm.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.virtualisation.appvm;
+
+in {
+
+  options = {
+    virtualisation.appvm = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          This enables AppVMs and related virtualisation settings.
+        '';
+      };
+      user = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          AppVM user login. Currently only AppVMs are supported for a single user only.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    virtualisation.libvirtd = {
+      enable = true;
+      qemu.verbatimConfig = ''
+        namespaces = []
+        user = "${cfg.user}"
+        group = "users"
+        remember_owner = 0
+      '';
+    };
+
+    users.users."${cfg.user}" = {
+      packages = [ pkgs.appvm ];
+      extraGroups = [ "libvirtd" ];
+    };
+
+  };
+
+}
+
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index e2425b44eac4..abe6455a1a69 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -17,7 +17,7 @@ let
 
     patches = [ ./azure-agent-entropy.patch ];
 
-    buildInputs = [ makeWrapper python pythonPackages.wrapPython ];
+    nativeBuildInputs = [ makeWrapper python pythonPackages.wrapPython ];
     runtimeDeps = [ findutils gnugrep gawk coreutils openssl openssh
                     nettools # for hostname
                     procps # for pidof
@@ -60,15 +60,15 @@ in
   options.virtualisation.azure.agent = {
     enable = mkOption {
       default = false;
-      description = "Whether to enable the Windows Azure Linux Agent.";
+      description = lib.mdDoc "Whether to enable the Windows Azure Linux Agent.";
     };
     verboseLogging = mkOption {
       default = false;
-      description = "Whether to enable verbose logging.";
+      description = lib.mdDoc "Whether to enable verbose logging.";
     };
     mountResourceDisk = mkOption {
       default = true;
-      description = "Whether the agent should format (ext4) and mount the resource disk to /mnt/resource.";
+      description = lib.mdDoc "Whether the agent should format (ext4) and mount the resource disk to /mnt/resource.";
     };
   };
 
diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix
index 03dd3c051309..17cfd3938305 100644
--- a/nixos/modules/virtualisation/azure-image.nix
+++ b/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/nixos/modules/virtualisation/build-vm.nix b/nixos/modules/virtualisation/build-vm.nix
index 4a4694950f98..e94254416316 100644
--- a/nixos/modules/virtualisation/build-vm.nix
+++ b/nixos/modules/virtualisation/build-vm.nix
@@ -25,8 +25,8 @@ in
   options = {
 
     virtualisation.vmVariant = mkOption {
-      description = ''
-        Machine configuration to be added for the vm script produced by <literal>nixos-rebuild build-vm</literal>.
+      description = lib.mdDoc ''
+        Machine configuration to be added for the vm script produced by `nixos-rebuild build-vm`.
       '';
       inherit (vmVariant) type;
       default = {};
@@ -34,8 +34,8 @@ in
     };
 
     virtualisation.vmVariantWithBootLoader = mkOption {
-      description = ''
-        Machine configuration to be added for the vm script produced by <literal>nixos-rebuild build-vm-with-bootloader</literal>.
+      description = lib.mdDoc ''
+        Machine configuration to be added for the vm script produced by `nixos-rebuild build-vm-with-bootloader`.
       '';
       inherit (vmVariantWithBootLoader) type;
       default = {};
diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix
index 0966ef84827f..2460ec45e3fc 100644
--- a/nixos/modules/virtualisation/container-config.nix
+++ b/nixos/modules/virtualisation/container-config.nix
@@ -7,8 +7,14 @@ 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
-    services.udisks2.enable = mkDefault false;
     powerManagement.enable = mkDefault false;
     documentation.nixos.enable = mkDefault false;
 
@@ -17,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/nixos/modules/virtualisation/containerd.nix b/nixos/modules/virtualisation/containerd.nix
index ea89a994b172..f6e3c8387298 100644
--- a/nixos/modules/virtualisation/containerd.nix
+++ b/nixos/modules/virtualisation/containerd.nix
@@ -19,11 +19,11 @@ 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;
-      description = ''
+      description = lib.mdDoc ''
        Path to containerd config file.
        Setting this option will override any configuration applied by the settings option.
       '';
@@ -33,14 +33,14 @@ in
     settings = lib.mkOption {
       type = settingsFormat.type;
       default = {};
-      description = ''
+      description = lib.mdDoc ''
         Verbatim lines to add to containerd.toml
       '';
     };
 
     args = lib.mkOption {
       default = {};
-      description = "extra args to append to the containerd cmdline";
+      description = lib.mdDoc "extra args to append to the containerd cmdline";
       type = attrsOf str;
     };
   };
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index cea3d51d3aef..fb9c19d79c13 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -8,30 +8,16 @@ let
 in
 {
   meta = {
-    maintainers = [] ++ lib.teams.podman.members;
+    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 =
       mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This option enables the common /etc/containers configuration module.
         '';
       };
@@ -39,13 +25,13 @@ in
     ociSeccompBpfHook.enable = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable the OCI seccomp BPF hook";
+      description = lib.mdDoc "Enable the OCI seccomp BPF hook";
     };
 
     containersConf.settings = mkOption {
       type = toml.type;
       default = { };
-      description = "containers.conf configuration";
+      description = lib.mdDoc "containers.conf configuration";
     };
 
     containersConf.cniPlugins = mkOption {
@@ -60,7 +46,7 @@ in
           pkgs.cniPlugins.dnsname
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         CNI plugins to install on the system.
       '';
     };
@@ -74,37 +60,37 @@ in
           runroot = "/run/containers/storage";
         };
       };
-      description = "storage.conf configuration";
+      description = lib.mdDoc "storage.conf configuration";
     };
 
     registries = {
       search = mkOption {
         type = types.listOf types.str;
         default = [ "docker.io" "quay.io" ];
-        description = ''
+        description = lib.mdDoc ''
           List of repositories to search.
         '';
       };
 
       insecure = mkOption {
-        default = [];
+        default = [ ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of insecure repositories.
         '';
       };
 
       block = mkOption {
-        default = [];
+        default = [ ];
         type = types.listOf types.str;
-        description = ''
+        description = lib.mdDoc ''
           List of blocked repositories.
         '';
       };
     };
 
     policy = mkOption {
-      default = {};
+      default = { };
       type = types.attrs;
       example = literalExpression ''
         {
@@ -116,10 +102,10 @@ in
           };
         }
       '';
-      description = ''
+      description = lib.mdDoc ''
         Signature verification policy file.
         If this option is empty the default policy file from
-        <literal>skopeo</literal> will be used.
+        `skopeo` will be used.
       '';
     };
 
@@ -149,7 +135,7 @@ in
     };
 
     environment.etc."containers/policy.json".source =
-      if cfg.policy != {} then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
+      if cfg.policy != { } then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
       else utils.copyFile "${pkgs.skopeo.src}/default-policy.json";
   };
 
diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix
index cf5110001503..95ce1fea58bb 100644
--- a/nixos/modules/virtualisation/cri-o.nix
+++ b/nixos/modules/virtualisation/cri-o.nix
@@ -11,47 +11,43 @@ let
   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" ];
       default = "overlay";
-      description = "Storage driver to be used";
+      description = lib.mdDoc "Storage driver to be used";
     };
 
     logLevel = mkOption {
       type = types.enum [ "trace" "debug" "info" "warn" "error" "fatal" ];
       default = "info";
-      description = "Log level to be used";
+      description = lib.mdDoc "Log level to be used";
     };
 
     pauseImage = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Override the default pause image for pod sandboxes";
+      description = lib.mdDoc "Override the default pause image for pod sandboxes";
       example = "k8s.gcr.io/pause:3.2";
     };
 
     pauseCommand = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Override the default pause command";
+      description = lib.mdDoc "Override the default pause command";
       example = "/pause";
     };
 
     runtime = mkOption {
       type = types.nullOr types.str;
       default = null;
-      description = "Override the default runtime";
+      description = lib.mdDoc "Override the default runtime";
       example = "crun";
     };
 
@@ -63,7 +59,7 @@ in
           pkgs.gvisor
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra packages to be installed in the CRI-O wrapper.
       '';
     };
@@ -71,12 +67,8 @@ in
     package = mkOption {
       type = types.package;
       default = crioPackage;
-      defaultText = literalDocBook ''
-        <literal>pkgs.cri-o</literal> built with
-        <literal>config.${opt.extraPackages}</literal>.
-      '';
       internal = true;
-      description = ''
+      description = lib.mdDoc ''
         The final CRI-O package (including extra packages).
       '';
     };
@@ -84,16 +76,16 @@ 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;
     };
 
     settings = mkOption {
       type = format.type;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Configuration for cri-o, see
-        <link xlink:href="https://github.com/cri-o/cri-o/blob/master/docs/crio.conf.5.md"/>.
+        <https://github.com/cri-o/cri-o/blob/master/docs/crio.conf.5.md>.
       '';
     };
   };
diff --git a/nixos/modules/virtualisation/digital-ocean-config.nix b/nixos/modules/virtualisation/digital-ocean-config.nix
index 88cb0cd450e8..754bc1a51857 100644
--- a/nixos/modules/virtualisation/digital-ocean-config.nix
+++ b/nixos/modules/virtualisation/digital-ocean-config.nix
@@ -10,19 +10,19 @@ with lib;
       type = bool;
       default = false;
       example = true;
-      description = "Whether to set the root password from the Digital Ocean metadata";
+      description = lib.mdDoc "Whether to set the root password from the Digital Ocean metadata";
     };
     setSshKeys = mkOption {
       type = bool;
       default = true;
       example = true;
-      description = "Whether to fetch ssh keys from Digital Ocean";
+      description = lib.mdDoc "Whether to fetch ssh keys from Digital Ocean";
     };
     seedEntropy = mkOption {
       type = bool;
       default = true;
       example = true;
-      description = "Whether to run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
+      description = lib.mdDoc "Whether to run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
     };
   };
   config =
diff --git a/nixos/modules/virtualisation/digital-ocean-image.nix b/nixos/modules/virtualisation/digital-ocean-image.nix
index 0ff2ee591f24..a57c89245f2e 100644
--- a/nixos/modules/virtualisation/digital-ocean-image.nix
+++ b/nixos/modules/virtualisation/digital-ocean-image.nix
@@ -13,7 +13,7 @@ in
       type = with types; either (enum [ "auto" ]) int;
       default = "auto";
       example = 4096;
-      description = ''
+      description = lib.mdDoc ''
         Size of disk image. Unit is MB.
       '';
     };
@@ -21,12 +21,12 @@ in
     virtualisation.digitalOceanImage.configFile = mkOption {
       type = with types; nullOr path;
       default = null;
-      description = ''
+      description = lib.mdDoc ''
         A path to a configuration file which will be placed at
-        <literal>/etc/nixos/configuration.nix</literal> and be used when switching
-        to a new configuration. If set to <literal>null</literal>, a default
+        `/etc/nixos/configuration.nix` and be used when switching
+        to a new configuration. If set to `null`, a default
         configuration is used that imports
-        <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+        `(modulesPath + "/virtualisation/digital-ocean-config.nix")`.
       '';
     };
 
@@ -34,7 +34,7 @@ in
       type = types.enum [ "gzip" "bzip2" ];
       default = "gzip";
       example = "bzip2";
-      description = ''
+      description = lib.mdDoc ''
         Disk image compression method. Choose bzip2 to generate smaller images that
         take longer to generate but will consume less metered storage space on your
         Digital Ocean account.
diff --git a/nixos/modules/virtualisation/digital-ocean-init.nix b/nixos/modules/virtualisation/digital-ocean-init.nix
index 4339d91de168..1a5d4e898e96 100644
--- a/nixos/modules/virtualisation/digital-ocean-init.nix
+++ b/nixos/modules/virtualisation/digital-ocean-init.nix
@@ -15,18 +15,18 @@ in {
     type = types.bool;
     default = true;
     example = true;
-    description = "Whether to reconfigure the system from Digital Ocean user data";
+    description = lib.mdDoc "Whether to reconfigure the system from Digital Ocean user data";
   };
   options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
     type = types.path;
     default = defaultConfigFile;
-    defaultText = literalDocBook ''
+    defaultText = literalMD ''
       The default configuration imports user-data if applicable and
-      <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+      `(modulesPath + "/virtualisation/digital-ocean-config.nix")`.
     '';
-    description = ''
+    description = lib.mdDoc ''
       A path to a configuration file which will be placed at
-      <literal>/etc/nixos/configuration.nix</literal> and be used when switching to
+      `/etc/nixos/configuration.nix` and be used when switching to
       a new configuration.
     '';
   };
@@ -46,7 +46,7 @@ in {
         RemainAfterExit = true;
       };
       restartIfChanged = false;
-      path = [ pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.systemd config.nix.package config.system.build.nixos-rebuild ];
+      path = [ pkgs.jq pkgs.gnused pkgs.gnugrep config.systemd.package config.nix.package config.system.build.nixos-rebuild ];
       environment = {
         HOME = "/root";
         NIX_PATH = concatStringsSep ":" [
diff --git a/nixos/modules/virtualisation/docker-rootless.nix b/nixos/modules/virtualisation/docker-rootless.nix
index d371f67ecdc8..f4e4bdc0963a 100644
--- a/nixos/modules/virtualisation/docker-rootless.nix
+++ b/nixos/modules/virtualisation/docker-rootless.nix
@@ -18,18 +18,18 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         This option enables docker in a rootless mode, a daemon that manages
         linux containers. To interact with the daemon, one needs to set
-        <command>DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock</command>.
+        {command}`DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock`.
       '';
     };
 
     setSocketVariable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Point <command>DOCKER_HOST</command> to rootless Docker instance for
+      description = lib.mdDoc ''
+        Point {command}`DOCKER_HOST` to rootless Docker instance for
         normal users by default.
       '';
     };
@@ -41,7 +41,7 @@ in
         ipv6 = true;
         "fixed-cidr-v6" = "fd00::/80";
       };
-      description = ''
+      description = lib.mdDoc ''
         Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
         See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
       '';
@@ -51,8 +51,7 @@ in
       default = pkgs.docker;
       defaultText = literalExpression "pkgs.docker";
       type = types.package;
-      example = literalExpression "pkgs.docker-edge";
-      description = ''
+      description = lib.mdDoc ''
         Docker package to be used in the module.
       '';
     };
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index a69cbe55c784..d9bd10ba1fc8 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -21,11 +21,11 @@ in
         type = types.bool;
         default = false;
         description =
-          ''
+          lib.mdDoc ''
             This option enables docker, a daemon that manages
             linux containers. Users in the "docker" group can interact with
             the daemon (e.g. to start or stop containers) using the
-            <command>docker</command> command line tool.
+            {command}`docker` command line tool.
           '';
       };
 
@@ -34,7 +34,7 @@ in
         type = types.listOf types.str;
         default = ["/run/docker.sock"];
         description =
-          ''
+          lib.mdDoc ''
             A list of unix and tcp docker should listen to. The format follows
             ListenStream as described in systemd.socket(5).
           '';
@@ -45,10 +45,10 @@ in
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             When enabled dockerd is started on boot. This is required for
             containers which are created with the
-            <literal>--restart=always</literal> flag to work. If this option is
+            `--restart=always` flag to work. If this option is
             disabled, docker might be started on demand by socket activation.
           '';
       };
@@ -61,7 +61,7 @@ in
           ipv6 = true;
           "fixed-cidr-v6" = "fd00::/80";
         };
-        description = ''
+        description = lib.mdDoc ''
           Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
           See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
         '';
@@ -71,7 +71,7 @@ in
       mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable nvidia-docker wrapper, supporting NVIDIA GPUs inside docker containers.
         '';
       };
@@ -81,7 +81,7 @@ in
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             Allow dockerd to be restarted without affecting running container.
             This option is incompatible with docker swarm.
           '';
@@ -92,7 +92,7 @@ in
         type = types.nullOr (types.enum ["aufs" "btrfs" "devicemapper" "overlay" "overlay2" "zfs"]);
         default = null;
         description =
-          ''
+          lib.mdDoc ''
             This option determines which Docker storage driver to use. By default
             it let's docker automatically choose preferred storage driver.
           '';
@@ -103,7 +103,7 @@ in
         type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs"];
         default = "journald";
         description =
-          ''
+          lib.mdDoc ''
             This option determines which Docker log driver to use.
           '';
       };
@@ -113,9 +113,9 @@ in
         type = types.separatedString " ";
         default = "";
         description =
-          ''
+          lib.mdDoc ''
             The extra command-line options to pass to
-            <command>docker</command> daemon.
+            {command}`docker` daemon.
           '';
       };
 
@@ -123,10 +123,10 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Whether to periodically prune Docker resources. If enabled, a
-          systemd timer will run <literal>docker system prune -f</literal>
-          as specified by the <literal>dates</literal> option.
+          systemd timer will run `docker system prune -f`
+          as specified by the `dates` option.
         '';
       };
 
@@ -134,18 +134,17 @@ in
         type = types.listOf types.str;
         default = [];
         example = [ "--all" ];
-        description = ''
-          Any additional flags passed to <command>docker system prune</command>.
+        description = lib.mdDoc ''
+          Any additional flags passed to {command}`docker system prune`.
         '';
       };
 
       dates = mkOption {
         default = "weekly";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Specification (in the format described by
-          <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>) of the time at
+          {manpage}`systemd.time(7)`) of the time at
           which the prune will occur.
         '';
       };
@@ -155,8 +154,7 @@ in
       default = pkgs.docker;
       defaultText = literalExpression "pkgs.docker";
       type = types.package;
-      example = literalExpression "pkgs.docker-edge";
-      description = ''
+      description = lib.mdDoc ''
         Docker package to be used in the module.
       '';
     };
@@ -223,6 +221,8 @@ in
         '';
 
         startAt = optional cfg.autoPrune.enable cfg.autoPrune.dates;
+        after = [ "docker.service" ];
+        requires = [ "docker.service" ];
       };
 
       assertions = [
diff --git a/nixos/modules/virtualisation/ec2-data.nix b/nixos/modules/virtualisation/ec2-data.nix
index 1b764e7e4d80..0cc6d9938e22 100644
--- a/nixos/modules/virtualisation/ec2-data.nix
+++ b/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/nixos/modules/virtualisation/ec2-metadata-fetcher.nix b/nixos/modules/virtualisation/ec2-metadata-fetcher.nix
deleted file mode 100644
index 760f024f33fb..000000000000
--- a/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/nixos/modules/virtualisation/ec2-metadata-fetcher.sh b/nixos/modules/virtualisation/ec2-metadata-fetcher.sh
new file mode 100644
index 000000000000..9e204d45dbd8
--- /dev/null
+++ b/nixos/modules/virtualisation/ec2-metadata-fetcher.sh
@@ -0,0 +1,67 @@
+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() {
+  # Intentionally no --fail here, so that we proceed even if e.g. a
+  # 404 was returned (but we still fail if we can't reach the IMDS
+  # server).
+  curl --silent --show-error --header "X-aws-ec2-metadata-token: $IMDS_TOKEN" "$@"
+}
+
+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/nixos/modules/virtualisation/ecs-agent.nix b/nixos/modules/virtualisation/ecs-agent.nix
index aa38a02ea088..dd87df9a2780 100644
--- a/nixos/modules/virtualisation/ecs-agent.nix
+++ b/nixos/modules/virtualisation/ecs-agent.nix
@@ -6,18 +6,18 @@ 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;
-      description = "The ECS agent package to use";
+      description = lib.mdDoc "The ECS agent package to use";
       default = pkgs.ecs-agent;
       defaultText = literalExpression "pkgs.ecs-agent";
     };
 
     extra-environment = mkOption {
       type = types.attrsOf types.str;
-      description = "The environment the ECS agent should run with. See the ECS agent documentation for keys that work here.";
+      description = lib.mdDoc "The environment the ECS agent should run with. See the ECS agent documentation for keys that work here.";
       default = {};
     };
   };
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 0c72696f802b..197ebb18b9ad 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/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/nixos/modules/virtualisation/hyperv-guest.nix b/nixos/modules/virtualisation/hyperv-guest.nix
index fb6502644b80..cba4f92abe82 100644
--- a/nixos/modules/virtualisation/hyperv-guest.nix
+++ b/nixos/modules/virtualisation/hyperv-guest.nix
@@ -8,13 +8,13 @@ let
 in {
   options = {
     virtualisation.hypervGuest = {
-      enable = mkEnableOption "Hyper-V Guest Support";
+      enable = mkEnableOption (lib.mdDoc "Hyper-V Guest Support");
 
       videoMode = mkOption {
         type = types.str;
         default = "1152x864";
         example = "1024x768";
-        description = ''
+        description = lib.mdDoc ''
           Resolution at which to initialize the video adapter.
 
           Supports screen resolution up to Full HD 1920x1080 with 32 bit color
@@ -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/nixos/modules/virtualisation/hyperv-image.nix b/nixos/modules/virtualisation/hyperv-image.nix
index 6845d6750092..efaea0c110d2 100644
--- a/nixos/modules/virtualisation/hyperv-image.nix
+++ b/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/nixos/modules/virtualisation/includes-to-excludes.py b/nixos/modules/virtualisation/includes-to-excludes.py
new file mode 100644
index 000000000000..05ef9c0f23b9
--- /dev/null
+++ b/nixos/modules/virtualisation/includes-to-excludes.py
@@ -0,0 +1,86 @@
+
+# Convert a list of strings to a regex that matches everything but those strings
+# ... and it had to be a POSIX regex; no negative lookahead :(
+# This is a workaround for erofs supporting only exclude regex, not an include list
+
+import sys
+import re
+from collections import defaultdict
+
+# We can configure this script to match in different ways if we need to.
+# The regex got too long for the argument list, so we had to truncate the
+# hashes and use MATCH_STRING_PREFIX. That's less accurate, and might pick up some
+# garbage like .lock files, but only if the sandbox doesn't hide those. Even
+# then it should be harmless.
+
+# Produce the negation of ^a$
+MATCH_EXACTLY = ".+"
+# Produce the negation of ^a
+MATCH_STRING_PREFIX = "//X" # //X should be epsilon regex instead. Not supported??
+# Produce the negation of ^a/?
+MATCH_SUBPATHS = "[^/].*$"
+
+# match_end = MATCH_SUBPATHS
+match_end = MATCH_STRING_PREFIX
+# match_end = MATCH_EXACTLY
+
+def chars_to_inverted_class(letters):
+    assert len(letters) > 0
+    letters = list(letters)
+
+    s = "[^"
+
+    if "]" in letters:
+        s += "]"
+        letters.remove("]")
+
+    final = ""
+    if "-" in letters:
+        final = "-"
+        letters.remove("-")
+
+    s += "".join(letters)
+
+    s += final
+
+    s += "]"
+
+    return s
+
+# There's probably at least one bug in here, but it seems to works well enough
+# for filtering store paths.
+def strings_to_inverted_regex(strings):
+    s = "("
+
+    # Match anything that starts with the wrong character
+
+    chars = defaultdict(list)
+
+    for item in strings:
+        if item != "":
+            chars[item[0]].append(item[1:])
+
+    if len(chars) == 0:
+        s += match_end
+    else:
+        s += chars_to_inverted_class(chars)
+
+    # Now match anything that starts with the right char, but then goes wrong
+
+    for char, sub in chars.items():
+        s += "|(" + re.escape(char) + strings_to_inverted_regex(sub) + ")"
+
+    s += ")"
+    return s
+
+if __name__ == "__main__":
+    stdin_lines = []
+    for line in sys.stdin:
+        if line.strip() != "":
+            stdin_lines.append(line.strip())
+
+    print("^" + strings_to_inverted_regex(stdin_lines))
+
+# Test:
+# (echo foo; echo fo/; echo foo/; echo foo/ba/r; echo b; echo az; echo az/; echo az/a; echo ab; echo ab/a; echo ab/; echo abc; echo abcde; echo abb; echo ac; echo b) | grep -vE "$((echo ab; echo az; echo foo;) | python includes-to-excludes.py | tee /dev/stderr )"
+# should print ab, az, foo and their subpaths
diff --git a/nixos/modules/virtualisation/kvmgt.nix b/nixos/modules/virtualisation/kvmgt.nix
index 5e7a73bec902..1e02636f81f4 100644
--- a/nixos/modules/virtualisation/kvmgt.nix
+++ b/nixos/modules/virtualisation/kvmgt.nix
@@ -10,30 +10,30 @@ 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;
         default = "0000:00:02.0";
-        description = "PCI ID of graphics card. You can figure it with <command>ls /sys/class/mdev_bus</command>.";
+        description = lib.mdDoc "PCI ID of graphics card. You can figure it with {command}`ls /sys/class/mdev_bus`.";
       };
       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/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index e0bccb83a97f..3b30bc8c4165 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -11,10 +11,9 @@ let
     auth_unix_rw = "polkit"
     ${cfg.extraConfig}
   '';
-  ovmfFilePrefix = if pkgs.stdenv.isAarch64 then "AAVMF" else "OVMF";
   qemuConfigFile = pkgs.writeText "qemu.conf" ''
     ${optionalString cfg.qemu.ovmf.enable ''
-      nvram = [ "/run/libvirt/nix-ovmf/${ovmfFilePrefix}_CODE.fd:/run/libvirt/nix-ovmf/${ovmfFilePrefix}_VARS.fd" ]
+      nvram = [ "/run/libvirt/nix-ovmf/AAVMF_CODE.fd:/run/libvirt/nix-ovmf/AAVMF_VARS.fd", "/run/libvirt/nix-ovmf/OVMF_CODE.fd:/run/libvirt/nix-ovmf/OVMF_VARS.fd" ]
     ''}
     ${optionalString (!cfg.qemu.runAsRoot) ''
       user = "qemu-libvirtd"
@@ -30,19 +29,26 @@ let
       enable = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           Allows libvirtd to take advantage of OVMF when creating new
           QEMU VMs with UEFI boot.
         '';
       };
 
+      # mkRemovedOptionModule does not work in submodules, do it manually
       package = mkOption {
-        type = types.package;
-        default = pkgs.OVMF;
-        defaultText = literalExpression "pkgs.OVMF";
-        example = literalExpression "pkgs.OVMFFull";
-        description = ''
-          OVMF package to use.
+        type = types.nullOr types.package;
+        default = null;
+        internal = true;
+      };
+
+      packages = mkOption {
+        type = types.listOf types.package;
+        default = [ pkgs.OVMF.fd ];
+        defaultText = literalExpression "[ pkgs.OVMF.fd ]";
+        example = literalExpression "[ pkgs.OVMFFull.fd pkgs.pkgsCross.aarch64-multiplatform.OVMF.fd ]";
+        description = lib.mdDoc ''
+          List of OVMF packages to use. Each listed package must contain files names FV/OVMF_CODE.fd and FV/OVMF_VARS.fd or FV/AAVMF_CODE.fd and FV/AAVMF_VARS.fd
         '';
       };
     };
@@ -53,7 +59,7 @@ let
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Allows libvirtd to use swtpm to create an emulated TPM.
         '';
       };
@@ -62,7 +68,7 @@ let
         type = types.package;
         default = pkgs.swtpm;
         defaultText = literalExpression "pkgs.swtpm";
-        description = ''
+        description = lib.mdDoc ''
           swtpm package to use.
         '';
       };
@@ -75,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.
@@ -85,7 +91,7 @@ let
       runAsRoot = mkOption {
         type = types.bool;
         default = true;
-        description = ''
+        description = lib.mdDoc ''
           If true,  libvirtd runs qemu as root.
           If false, libvirtd runs qemu as unprivileged user qemu-libvirtd.
           Changing this option to false may cause file permission issues
@@ -99,7 +105,7 @@ let
         default = ''
           namespaces = []
         '';
-        description = ''
+        description = lib.mdDoc ''
           Contents written to the qemu configuration file, qemu.conf.
           Make sure to include a proper namespace configuration when
           supplying custom configuration.
@@ -109,7 +115,7 @@ let
       ovmf = mkOption {
         type = ovmfModule;
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           QEMU's OVMF options.
         '';
       };
@@ -117,7 +123,7 @@ let
       swtpm = mkOption {
         type = swtpmModule;
         default = { };
-        description = ''
+        description = lib.mdDoc ''
           QEMU's swtpm options.
         '';
       };
@@ -141,9 +147,9 @@ in
     (mkRenamedOptionModule
       [ "virtualisation" "libvirtd" "qemuOvmf" ]
       [ "virtualisation" "libvirtd" "qemu" "ovmf" "enable" ])
-    (mkRenamedOptionModule
+    (mkRemovedOptionModule
       [ "virtualisation" "libvirtd" "qemuOvmfPackage" ]
-      [ "virtualisation" "libvirtd" "qemu" "ovmf" "package" ])
+      "If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.")
     (mkRenamedOptionModule
       [ "virtualisation" "libvirtd" "qemuSwtpm" ]
       [ "virtualisation" "libvirtd" "qemu" "swtpm" "enable" ])
@@ -156,11 +162,11 @@ in
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         This option enables libvirtd, a daemon that manages
         virtual machines. Users in the "libvirtd" group can interact with
         the daemon (e.g. to start or stop VMs) using the
-        <command>virsh</command> command line tool, among others.
+        {command}`virsh` command line tool, among others.
       '';
     };
 
@@ -168,7 +174,7 @@ in
       type = types.package;
       default = pkgs.libvirt;
       defaultText = literalExpression "pkgs.libvirt";
-      description = ''
+      description = lib.mdDoc ''
         libvirt package to use.
       '';
     };
@@ -176,7 +182,7 @@ in
     extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = ''
+      description = lib.mdDoc ''
         Extra contents appended to the libvirtd configuration file,
         libvirtd.conf.
       '';
@@ -186,7 +192,7 @@ in
       type = types.listOf types.str;
       default = [ ];
       example = [ "--verbose" ];
-      description = ''
+      description = lib.mdDoc ''
         Extra command line arguments passed to libvirtd on startup.
       '';
     };
@@ -194,7 +200,7 @@ in
     onBoot = mkOption {
       type = types.enum [ "start" "ignore" ];
       default = "start";
-      description = ''
+      description = lib.mdDoc ''
         Specifies the action to be done to / on the guests when the host boots.
         The "start" option starts all guests that were running prior to shutdown
         regardless of their autostart settings. The "ignore" option will not
@@ -206,7 +212,7 @@ in
     onShutdown = mkOption {
       type = types.enum [ "shutdown" "suspend" ];
       default = "suspend";
-      description = ''
+      description = lib.mdDoc ''
         When shutting down / restarting the host what method should
         be used to gracefully halt the guests. Setting to "shutdown"
         will cause an ACPI shutdown of each guest. "suspend" will
@@ -217,7 +223,7 @@ in
     allowedBridges = mkOption {
       type = types.listOf types.str;
       default = [ "virbr0" ];
-      description = ''
+      description = lib.mdDoc ''
         List of bridge devices that can be used by qemu:///session
       '';
     };
@@ -225,7 +231,7 @@ in
     qemu = mkOption {
       type = qemuModule;
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         QEMU related options.
       '';
     };
@@ -238,12 +244,15 @@ in
 
     assertions = [
       {
-        assertion = config.security.polkit.enable;
-        message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true').";
+        assertion = config.virtualisation.libvirtd.qemu.ovmf.package == null;
+        message = ''
+        The option virtualisation.libvirtd.qemu.ovmf.package is superseded by virtualisation.libvirtd.qemu.ovmf.packages.
+        If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.
+        '';
       }
       {
-        assertion = builtins.elem "fd" cfg.qemu.ovmf.package.outputs;
-        message = "The option 'virtualisation.libvirtd.qemuOvmfPackage' needs a package that has an 'fd' output.";
+        assertion = config.security.polkit.enable;
+        message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true').";
       }
     ];
 
@@ -273,7 +282,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 ];
@@ -284,7 +293,7 @@ in
         # Copy default libvirt network config .xml files to /var/lib
         # Files modified by the user will not be overwritten
         for i in $(cd ${cfg.package}/var/lib && echo \
-            libvirt/qemu/networks/*.xml libvirt/qemu/networks/autostart/*.xml \
+            libvirt/qemu/networks/*.xml \
             libvirt/nwfilter/*.xml );
         do
             mkdir -p /var/lib/$(dirname $i) -m 755
@@ -299,14 +308,22 @@ 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
 
-        ${optionalString cfg.qemu.ovmf.enable ''
-          ln -s --force ${cfg.qemu.ovmf.package.fd}/FV/${ovmfFilePrefix}_CODE.fd /run/${dirName}/nix-ovmf/
-          ln -s --force ${cfg.qemu.ovmf.package.fd}/FV/${ovmfFilePrefix}_VARS.fd /run/${dirName}/nix-ovmf/
-        ''}
+        ${optionalString cfg.qemu.ovmf.enable (let
+          ovmfpackage = pkgs.buildEnv {
+            name = "qemu-ovmf";
+            paths = cfg.qemu.ovmf.packages;
+          };
+        in
+          ''
+          ln -s --force ${ovmfpackage}/FV/AAVMF_CODE.fd /run/${dirName}/nix-ovmf/
+          ln -s --force ${ovmfpackage}/FV/OVMF_CODE.fd /run/${dirName}/nix-ovmf/
+          ln -s --force ${ovmfpackage}/FV/AAVMF_VARS.fd /run/${dirName}/nix-ovmf/
+          ln -s --force ${ovmfpackage}/FV/OVMF_VARS.fd /run/${dirName}/nix-ovmf/
+        '')}
       '';
 
       serviceConfig = {
@@ -319,6 +336,7 @@ in
     };
 
     systemd.services.libvirtd = {
+      wantedBy = [ "multi-user.target" ];
       requires = [ "libvirtd-config.service" ];
       after = [ "libvirtd-config.service" ]
         ++ optional vswitch.enable "ovs-vswitchd.service";
diff --git a/nixos/modules/virtualisation/linode-config.nix b/nixos/modules/virtualisation/linode-config.nix
new file mode 100644
index 000000000000..d664e8269f41
--- /dev/null
+++ b/nixos/modules/virtualisation/linode-config.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+with lib;
+{
+  imports = [ ../profiles/qemu-guest.nix ];
+
+  services.openssh = {
+    enable = true;
+
+    permitRootLogin = "prohibit-password";
+    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/nixos/modules/virtualisation/linode-image.nix b/nixos/modules/virtualisation/linode-image.nix
new file mode 100644
index 000000000000..f8d212d9cda0
--- /dev/null
+++ b/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; [ houstdav000 ];
+}
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index 9816cc2332fb..4963d9f3f9e4 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/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,30 +51,30 @@ 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 ''
           {
-            # create /etc/hostname on container creation
+            # create /etc/hostname on container creation. also requires networking.hostName = "" to be set
             "hostname" = {
               enable = true;
               target = "/etc/hostname";
-              template = builtins.writeFile "hostname.tpl" "{{ container.name }}";
+              template = builtins.toFile "hostname.tpl" "{{ container.name }}";
               when = [ "create" ];
             };
             # create /etc/nixos/hostname.nix with a configuration for keeping the hostname applied
             "hostname-nix" = {
               enable = true;
               target = "/etc/nixos/hostname.nix";
-              template = builtins.writeFile "hostname-nix.tpl" "{ ... }: { networking.hostName = "{{ container.name }}"; }";
+              template = builtins.toFile "hostname-nix.tpl" "{ ... }: { networking.hostName = \"{{ container.name }}\"; }";
               # copy keeps the file updated when the container is changed
               when = [ "create" "copy" ];
             };
@@ -82,12 +82,22 @@ in
             "configuration-nix" = {
               enable = true;
               target = "/etc/nixos/configuration.nix";
-              template = builtins.writeFile "configuration-nix" "{{ config_get(\"user.user-data\", properties.default) }}";
+              template = builtins.toFile "configuration-nix" "{{ config_get(\"user.user-data\", properties.default) }}";
               when = [ "create" ];
             };
           };
         '';
       };
+
+      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/).
+        '';
+      };
     };
   };
 
@@ -146,12 +156,31 @@ in
     };
 
     # Add the overrides from lxd distrobuilder
-    systemd.extraConfig = ''
-      [Service]
-      ProtectProc=default
-      ProtectControlGroups=no
-      ProtectKernelTunables=no
-    '';
+    # 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 +199,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/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix
index 0f8b22a45df0..5bd64a5f9a56 100644
--- a/nixos/modules/virtualisation/lxc.nix
+++ b/nixos/modules/virtualisation/lxc.nix
@@ -19,7 +19,7 @@ in
         type = types.bool;
         default = false;
         description =
-          ''
+          lib.mdDoc ''
             This enables Linux Containers (LXC), which provides tools
             for creating and managing system or application containers
             on Linux.
@@ -31,10 +31,9 @@ in
         type = types.lines;
         default = "";
         description =
-          ''
+          lib.mdDoc ''
             This is the system-wide LXC config. See
-            <citerefentry><refentrytitle>lxc.system.conf</refentrytitle>
-            <manvolnum>5</manvolnum></citerefentry>.
+            {manpage}`lxc.system.conf(5)`.
           '';
       };
 
@@ -43,10 +42,9 @@ in
         type = types.lines;
         default = "";
         description =
-          ''
+          lib.mdDoc ''
             Default config (default.conf) for new containers, i.e. for
-            network config. See <citerefentry><refentrytitle>lxc.container.conf
-            </refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+            network config. See {manpage}`lxc.container.conf(5)`.
           '';
       };
 
@@ -55,11 +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/nixos/modules/virtualisation/lxcfs.nix b/nixos/modules/virtualisation/lxcfs.nix
index b2457403463a..fb0ba49f7304 100644
--- a/nixos/modules/virtualisation/lxcfs.nix
+++ b/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/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 18451b147ff5..c06716e5eb60 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/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;
+          ```
         '';
       };
 
@@ -36,7 +36,7 @@ in {
         type = types.package;
         default = pkgs.lxd;
         defaultText = literalExpression "pkgs.lxd";
-        description = ''
+        description = lib.mdDoc ''
           The LXD package to use.
         '';
       };
@@ -45,7 +45,7 @@ in {
         type = types.package;
         default = pkgs.lxc;
         defaultText = literalExpression "pkgs.lxc";
-        description = ''
+        description = lib.mdDoc ''
           The LXC package to use with LXD (required for AppArmor profiles).
         '';
       };
@@ -54,7 +54,7 @@ in {
         type = types.bool;
         default = config.boot.zfs.enabled;
         defaultText = literalExpression "config.boot.zfs.enabled";
-        description = ''
+        description = lib.mdDoc ''
           Enables lxd to use zfs as a storage for containers.
 
           This option is enabled by default if a zfs pool is configured
@@ -65,7 +65,7 @@ in {
       recommendedSysctlSettings = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enables various settings to avoid common pitfalls when
           running containers requiring many file operations.
           Fixes errors like "Too many open files" or
@@ -79,7 +79,7 @@ in {
         type = types.int;
         default = 600;
         apply = toString;
-        description = ''
+        description = lib.mdDoc ''
           Time to wait (in seconds) for LXD to become ready to process requests.
           If LXD does not reply within the configured time, lxd.service will be
           considered failed and systemd will attempt to restart it.
@@ -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/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 0838a57f0f37..e1e640c44742 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -1,9 +1,18 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }@host:
 
 with lib;
 
 let
 
+  configurationPrefix = optionalString (versionAtLeast config.system.stateVersion "22.05") "nixos-";
+  configurationDirectoryName = "${configurationPrefix}containers";
+  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:
@@ -77,7 +86,7 @@ let
   startScript = cfg:
     ''
       mkdir -p -m 0755 "$root/etc" "$root/var/lib"
-      mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/containers
+      mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/nixos-containers
       if ! [ -e "$root/etc/os-release" ]; then
         touch "$root/etc/os-release"
       fi
@@ -127,12 +136,14 @@ let
 
       # If the host is 64-bit and the container is 32-bit, add a
       # --personality flag.
-      ${optionalString (config.nixpkgs.localSystem.system == "x86_64-linux") ''
+      ${optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
         if [ "$(< ''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system)" = i686-linux ]; then
           extraFlags+=" --personality=x86"
         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
@@ -243,17 +254,17 @@ 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"
       '';
 
     SyslogIdentifier = "container %i";
 
-    EnvironmentFile = "-/etc/containers/%i.conf";
+    EnvironmentFile = "-${configurationDirectory}/%i.conf";
 
     Type = "notify";
 
-    RuntimeDirectory = lib.optional cfg.ephemeral "containers/%i";
+    RuntimeDirectory = lib.optional cfg.ephemeral "${configurationDirectoryName}/%i";
 
     # Note that on reboot, systemd-nspawn returns 133, so this
     # unit will be restarted. On poweroff, it returns 0, so the
@@ -279,7 +290,6 @@ let
     DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices;
   };
 
-  system = config.nixpkgs.localSystem.system;
   kernelVersion = config.boot.kernelPackages.kernel.version;
 
   bindMountOpts = { name, ... }: {
@@ -288,18 +298,18 @@ let
       mountPoint = mkOption {
         example = "/mnt/usb";
         type = types.str;
-        description = "Mount point on the container file system.";
+        description = lib.mdDoc "Mount point on the container file system.";
       };
       hostPath = mkOption {
         default = null;
         example = "/home/alice";
         type = types.nullOr types.str;
-        description = "Location of the host path to be mounted.";
+        description = lib.mdDoc "Location of the host path to be mounted.";
       };
       isReadOnly = mkOption {
         default = true;
         type = types.bool;
-        description = "Determine whether the mounted path will be accessed in read-only mode.";
+        description = lib.mdDoc "Determine whether the mounted path will be accessed in read-only mode.";
       };
     };
 
@@ -314,16 +324,16 @@ let
       node = mkOption {
         example = "/dev/net/tun";
         type = types.str;
-        description = "Path to device node";
+        description = lib.mdDoc "Path to device node";
       };
       modifier = mkOption {
         example = "rw";
         type = types.str;
-        description = ''
+        description = lib.mdDoc ''
           Device node access modifier. Takes a combination
-          <literal>r</literal> (read), <literal>w</literal> (write), and
-          <literal>m</literal> (mknod). See the
-          <literal>systemd.resource-control(5)</literal> man page for more
+          `r` (read), `w` (write), and
+          `m` (mknod). See the
+          `systemd.resource-control(5)` man page for more
           information.'';
       };
     };
@@ -341,7 +351,7 @@ let
       type = types.nullOr types.str;
       default = null;
       example = "br0";
-      description = ''
+      description = lib.mdDoc ''
         Put the host-side of the veth-pair into the named bridge.
         Only one of hostAddress* or hostBridge can be given.
       '';
@@ -353,22 +363,22 @@ let
           protocol = mkOption {
             type = types.str;
             default = "tcp";
-            description = "The protocol specifier for port forwarding between host and container";
+            description = lib.mdDoc "The protocol specifier for port forwarding between host and container";
           };
           hostPort = mkOption {
             type = types.int;
-            description = "Source port of the external interface on host";
+            description = lib.mdDoc "Source port of the external interface on host";
           };
           containerPort = mkOption {
             type = types.nullOr types.int;
             default = null;
-            description = "Target port of container";
+            description = lib.mdDoc "Target port of container";
           };
         };
       });
       default = [];
       example = [ { protocol = "tcp"; hostPort = 8080; containerPort = 80; } ];
-      description = ''
+      description = lib.mdDoc ''
         List of forwarded ports from host to container. Each forwarded port
         is specified by protocol, hostPort and containerPort. By default,
         protocol is tcp and hostPort and containerPort are assumed to be
@@ -381,7 +391,7 @@ let
       type = types.nullOr types.str;
       default = null;
       example = "10.231.136.1";
-      description = ''
+      description = lib.mdDoc ''
         The IPv4 address assigned to the host interface.
         (Not used when hostBridge is set.)
       '';
@@ -391,7 +401,7 @@ let
       type = types.nullOr types.str;
       default = null;
       example = "fc00::1";
-      description = ''
+      description = lib.mdDoc ''
         The IPv6 address assigned to the host interface.
         (Not used when hostBridge is set.)
       '';
@@ -401,7 +411,7 @@ let
       type = types.nullOr types.str;
       default = null;
       example = "10.231.136.2";
-      description = ''
+      description = lib.mdDoc ''
         The IPv4 address assigned to the interface in the container.
         If a hostBridge is used, this should be given with netmask to access
         the whole network. Otherwise the default netmask is /32 and routing is
@@ -413,7 +423,7 @@ let
       type = types.nullOr types.str;
       default = null;
       example = "fc00::2";
-      description = ''
+      description = lib.mdDoc ''
         The IPv6 address assigned to the interface in the container.
         If a hostBridge is used, this should be given with netmask to access
         the whole network. Otherwise the default netmask is /128 and routing is
@@ -445,7 +455,7 @@ in
     boot.isContainer = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether this NixOS machine is a lightweight container running
         in another NixOS system.
       '';
@@ -454,7 +464,7 @@ in
     boot.enableContainers = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable support for NixOS containers. Defaults to true
         (at no cost if containers are not actually used).
       '';
@@ -466,19 +476,22 @@ in
         {
           options = {
             config = mkOption {
-              description = ''
+              description = lib.mdDoc ''
                 A specification of the desired configuration of this
                 container, as a NixOS module.
               '';
               type = lib.mkOptionType {
                 name = "Toplevel NixOS config";
                 merge = loc: defs: (import "${toString config.nixpkgs}/nixos/lib/eval-config.nix" {
-                  inherit system;
                   modules =
                     let
-                      extraConfig = {
+                      extraConfig = { options, ... }: {
                         _file = "module at ${__curPos.file}:${toString __curPos.line}";
                         config = {
+                          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;
@@ -508,9 +521,9 @@ in
             path = mkOption {
               type = types.path;
               example = "/nix/var/nix/profiles/per-container/webserver";
-              description = ''
+              description = lib.mdDoc ''
                 As an alternative to specifying
-                <option>config</option>, you can specify the path to
+                {option}`config`, you can specify the path to
                 the evaluated NixOS system configuration, typically a
                 symlink to a system profile.
               '';
@@ -520,7 +533,7 @@ in
               type = types.listOf types.str;
               default = [];
               example = [ "CAP_NET_ADMIN" "CAP_MKNOD" ];
-              description = ''
+              description = lib.mdDoc ''
                 Grant additional capabilities to the container.  See the
                 capabilities(7) and systemd-nspawn(1) man pages for more
                 information.
@@ -531,13 +544,13 @@ 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.
               '';
             };
@@ -545,7 +558,7 @@ in
             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.
@@ -553,8 +566,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.
@@ -564,21 +577,21 @@ in
             enableTun = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Allows the container to create and setup tunnel interfaces
-                by granting the <literal>NET_ADMIN</literal> capability and
-                enabling access to <literal>/dev/net/tun</literal>.
+                by granting the `NET_ADMIN` capability and
+                enabling access to `/dev/net/tun`.
               '';
             };
 
             privateNetwork = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether to give the container its own private virtual
                 Ethernet interface.  The interface is called
-                <literal>eth0</literal>, and is hooked up to the interface
-                <literal>ve-<replaceable>container-name</replaceable></literal>
+                `eth0`, and is hooked up to the interface
+                `ve-«container-name»`
                 on the host.  If this option is not set, then the
                 container shares the network interfaces of the host,
                 and can bind to any port on any interface.
@@ -589,7 +602,7 @@ in
               type = types.listOf types.str;
               default = [];
               example = [ "eth1" "eth2" ];
-              description = ''
+              description = lib.mdDoc ''
                 The list of interfaces to be moved into the container.
               '';
             };
@@ -598,7 +611,7 @@ in
               type = types.listOf types.str;
               default = [];
               example = [ "eth1" "eth2" ];
-              description = ''
+              description = lib.mdDoc ''
                 The list of host interfaces from which macvlans will be
                 created. For each interface specified, a macvlan interface
                 will be created and moved to the container.
@@ -608,7 +621,7 @@ in
             extraVeths = mkOption {
               type = with types; attrsOf (submodule { options = networkOptions; });
               default = {};
-              description = ''
+              description = lib.mdDoc ''
                 Extra veth-pairs to be created for the container.
               '';
             };
@@ -616,7 +629,7 @@ in
             autoStart = mkOption {
               type = types.bool;
               default = false;
-              description = ''
+              description = lib.mdDoc ''
                 Whether the container is automatically started at boot-time.
               '';
             };
@@ -624,11 +637,10 @@ in
             timeoutStartSec = mkOption {
               type = types.str;
               default = "1min";
-              description = ''
+              description = lib.mdDoc ''
                 Time for the container to start. In case of a timeout,
                 the container processes get killed.
-                See <citerefentry><refentrytitle>systemd.time</refentrytitle>
-                <manvolnum>7</manvolnum></citerefentry>
+                See {manpage}`systemd.time(7)`
                 for more information about the format.
                '';
             };
@@ -643,7 +655,7 @@ in
               '';
 
               description =
-                ''
+                lib.mdDoc ''
                   An extra list of directories that is bound to the container.
                 '';
             };
@@ -652,7 +664,7 @@ in
               type = with types; listOf (submodule allowedDeviceOpts);
               default = [];
               example = [ { node = "/dev/net/tun"; modifier = "rw"; } ];
-              description = ''
+              description = lib.mdDoc ''
                 A list of device nodes to which the containers has access to.
               '';
             };
@@ -661,7 +673,7 @@ in
               type = types.listOf types.str;
               default = [];
               example = [ "/var" ];
-              description = ''
+              description = lib.mdDoc ''
                 Mounts a set of tmpfs file systems into the container.
                 Multiple paths can be specified.
                 Valid items must conform to the --tmpfs argument
@@ -673,7 +685,7 @@ in
               type = types.listOf types.str;
               default = [];
               example = [ "--drop-capability=CAP_SYS_CHROOT" ];
-              description = ''
+              description = lib.mdDoc ''
                 Extra flags passed to the systemd-nspawn command.
                 See systemd-nspawn(1) for details.
               '';
@@ -716,19 +728,19 @@ in
               { config =
                   { config, pkgs, ... }:
                   { services.postgresql.enable = true;
-                    services.postgresql.package = pkgs.postgresql_10;
+                    services.postgresql.package = pkgs.postgresql_14;
 
                     system.stateVersion = "21.05";
                   };
               };
           }
         '';
-      description = ''
+      description = lib.mdDoc ''
         A set of NixOS system configurations to be run as lightweight
         containers.  Each container appears as a service
-        <literal>container-<replaceable>name</replaceable></literal>
+        `container-«name»`
         on the host system, allowing it to be started and stopped via
-        <command>systemctl</command>.
+        {command}`systemctl`.
       '';
     };
 
@@ -740,12 +752,12 @@ in
     unit = {
       description = "Container '%i'";
 
-      unitConfig.RequiresMountsFor = "/var/lib/containers/%i";
+      unitConfig.RequiresMountsFor = "${stateDirectory}/%i";
 
       path = [ pkgs.iproute2 ];
 
       environment = {
-        root = "/var/lib/containers/%i";
+        root = "${stateDirectory}/%i";
         INSTANCE = "%i";
       };
 
@@ -760,6 +772,11 @@ in
       serviceConfig = serviceDirectives dummyConfig;
     };
   in {
+    warnings =
+      (optional (config.virtualisation.containers.enable && versionOlder config.system.stateVersion "22.05") ''
+        Enabling both boot.enableContainers & virtualisation.containers on system.stateVersion < 22.05 is unsupported.
+      '');
+
     systemd.targets.multi-user.wants = [ "machines.target" ];
 
     systemd.services = listToAttrs (filter (x: x.value != null) (
@@ -782,8 +799,8 @@ in
             script = startScript containerConfig;
             postStart = postStartScript containerConfig;
             serviceConfig = serviceDirectives containerConfig;
-            unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "/var/lib/containers/%i";
-            environment.root = if containerConfig.ephemeral then "/run/containers/%i" else "/var/lib/containers/%i";
+            unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "${stateDirectory}/%i";
+            environment.root = if containerConfig.ephemeral then "/run/nixos-containers/%i" else "${stateDirectory}/%i";
           } // (
           if containerConfig.autoStart then
             {
@@ -792,7 +809,7 @@ in
               after = [ "network.target" ];
               restartTriggers = [
                 containerConfig.path
-                config.environment.etc."containers/${name}.conf".source
+                config.environment.etc."${configurationDirectoryName}/${name}.conf".source
               ];
               restartIfChanged = true;
             }
@@ -800,12 +817,12 @@ in
       )) config.containers)
     ));
 
-    # Generate a configuration file in /etc/containers for each
+    # Generate a configuration file in /etc/nixos-containers for each
     # container so that container@.target can get the container
     # configuration.
     environment.etc =
       let mkPortStr = p: p.protocol + ":" + (toString p.hostPort) + ":" + (if p.containerPort == null then toString p.hostPort else toString p.containerPort);
-      in mapAttrs' (name: cfg: nameValuePair "containers/${name}.conf"
+      in mapAttrs' (name: cfg: nameValuePair "${configurationDirectoryName}/${name}.conf"
       { text =
           ''
             SYSTEM_PATH=${cfg.path}
@@ -854,7 +871,9 @@ in
       ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1"
     '';
 
-    environment.systemPackages = [ pkgs.nixos-container ];
+    environment.systemPackages = [
+      nixos-container
+    ];
 
     boot.kernelModules = [
       "bridge"
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index f40481727830..61066c3cbd75 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -14,18 +14,18 @@ let
 
         image = mkOption {
           type = with types; str;
-          description = "OCI image to run.";
+          description = lib.mdDoc "OCI image to run.";
           example = "library/hello-world";
         };
 
         imageFile = mkOption {
           type = with types; nullOr package;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Path to an image file to load before running the image. This can
             be used to bypass pulling the image from the registry.
 
-            The <literal>image</literal> attribute must match the name and
+            The `image` attribute must match the name and
             tag of the image contained in this file, as they will be used to
             run the container with that image. If they do not match, the
             image will be pulled from the registry as usual.
@@ -38,20 +38,20 @@ let
           username = mkOption {
             type = with types; nullOr str;
             default = null;
-            description = "Username for login.";
+            description = lib.mdDoc "Username for login.";
           };
 
           passwordFile = mkOption {
             type = with types; nullOr str;
             default = null;
-            description = "Path to file containing password.";
+            description = lib.mdDoc "Path to file containing password.";
             example = "/etc/nixos/dockerhub-password.txt";
           };
 
           registry = mkOption {
             type = with types; nullOr str;
             default = null;
-            description = "Registry where to login to.";
+            description = lib.mdDoc "Registry where to login to.";
             example = "https://docker.pkg.github.com";
           };
 
@@ -60,7 +60,7 @@ let
         cmd = mkOption {
           type =  with types; listOf str;
           default = [];
-          description = "Commandline arguments to pass to the image's entrypoint.";
+          description = lib.mdDoc "Commandline arguments to pass to the image's entrypoint.";
           example = literalExpression ''
             ["--port=9000"]
           '';
@@ -68,7 +68,7 @@ let
 
         entrypoint = mkOption {
           type = with types; nullOr str;
-          description = "Override the default entrypoint of the image.";
+          description = lib.mdDoc "Override the default entrypoint of the image.";
           default = null;
           example = "/bin/my-app";
         };
@@ -76,7 +76,7 @@ let
         environment = mkOption {
           type = with types; attrsOf str;
           default = {};
-          description = "Environment variables to set for this container.";
+          description = lib.mdDoc "Environment variables to set for this container.";
           example = literalExpression ''
             {
               DATABASE_HOST = "db.example.com";
@@ -88,7 +88,7 @@ let
         environmentFiles = mkOption {
           type = with types; listOf path;
           default = [];
-          description = "Environment files for this container.";
+          description = lib.mdDoc "Environment files for this container.";
           example = literalExpression ''
             [
               /path/to/.env
@@ -100,15 +100,15 @@ let
         log-driver = mkOption {
           type = types.str;
           default = "journald";
-          description = ''
+          description = lib.mdDoc ''
             Logging driver for the container.  The default of
-            <literal>"journald"</literal> means that the container's logs will be
+            `"journald"` means that the container's logs will be
             handled as part of the systemd unit.
 
             For more details and a full list of logging drivers, refer to respective backends documentation.
 
             For Docker:
-            <link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">Docker engine documentation</link>
+            [Docker engine documentation](https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver)
 
             For Podman:
             Refer to the docker-run(1) man page.
@@ -118,49 +118,27 @@ let
         ports = mkOption {
           type = with types; listOf str;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             Network ports to publish from the container to the outer host.
 
             Valid formats:
+            - `<ip>:<hostPort>:<containerPort>`
+            - `<ip>::<containerPort>`
+            - `<hostPort>:<containerPort>`
+            - `<containerPort>`
 
-            <itemizedlist>
-              <listitem>
-                <para>
-                  <literal>&lt;ip&gt;:&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>&lt;ip&gt;::&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-              <listitem>
-                <para>
-                  <literal>&lt;containerPort&gt;</literal>
-                </para>
-              </listitem>
-            </itemizedlist>
-
-            Both <literal>hostPort</literal> and
-            <literal>containerPort</literal> can be specified as a range of
+            Both `hostPort` and `containerPort` can be specified as a range of
             ports.  When specifying ranges for both, the number of container
             ports in the range must match the number of host ports in the
-            range.  Example: <literal>1234-1236:1234-1236/tcp</literal>
+            range.  Example: `1234-1236:1234-1236/tcp`
 
-            When specifying a range for <literal>hostPort</literal> only, the
-            <literal>containerPort</literal> must <emphasis>not</emphasis> be a
-            range.  In this case, the container port is published somewhere
-            within the specified <literal>hostPort</literal> range.  Example:
-            <literal>1234-1236:1234/tcp</literal>
+            When specifying a range for `hostPort` only, the `containerPort`
+            must *not* be a range.  In this case, the container port is published
+            somewhere within the specified `hostPort` range.
+            Example: `1234-1236:1234/tcp`
 
             Refer to the
-            <link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
-            Docker engine documentation</link> for full details.
+            [Docker engine documentation](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) for full details.
           '';
           example = literalExpression ''
             [
@@ -172,7 +150,7 @@ let
         user = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = ''
+          description = lib.mdDoc ''
             Override the username or UID (and optionally groupname or GID) used
             in the container.
           '';
@@ -182,16 +160,15 @@ let
         volumes = mkOption {
           type = with types; listOf str;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             List of volumes to attach to this container.
 
-            Note that this is a list of <literal>"src:dst"</literal> strings to
-            allow for <literal>src</literal> to refer to
-            <literal>/nix/store</literal> paths, which would be difficult with an
-            attribute set.  There are also a variety of mount options available
-            as a third field; please refer to the
-            <link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems">
-            docker engine documentation</link> for details.
+            Note that this is a list of `"src:dst"` strings to
+            allow for `src` to refer to `/nix/store` paths, which
+            would be difficult with an attribute set.  There are
+            also a variety of mount options available as a third
+            field; please refer to the
+            [docker engine documentation](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) for details.
           '';
           example = literalExpression ''
             [
@@ -204,17 +181,17 @@ let
         workdir = mkOption {
           type = with types; nullOr str;
           default = null;
-          description = "Override the default working directory for the container.";
+          description = lib.mdDoc "Override the default working directory for the container.";
           example = "/var/lib/hello_world";
         };
 
         dependsOn = mkOption {
           type = with types; listOf str;
           default = [];
-          description = ''
+          description = lib.mdDoc ''
             Define which other containers this one depends on. They will be added to both After and Requires for the unit.
 
-            Use the same name as the attribute under <literal>virtualisation.oci-containers.containers</literal>.
+            Use the same name as the attribute under `virtualisation.oci-containers.containers`.
           '';
           example = literalExpression ''
             virtualisation.oci-containers.containers = {
@@ -229,7 +206,7 @@ let
         extraOptions = mkOption {
           type = with types; listOf str;
           default = [];
-          description = "Extra options for <command>${defaultBackend} run</command>.";
+          description = lib.mdDoc "Extra options for {command}`${defaultBackend} run`.";
           example = literalExpression ''
             ["--network=host"]
           '';
@@ -238,7 +215,7 @@ let
         autoStart = mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             When enabled, the container is automatically started on boot.
             If this option is set to false, the container has to be started on-demand via its service.
           '';
@@ -250,6 +227,7 @@ 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;
@@ -273,16 +251,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}"
@@ -293,8 +280,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
@@ -316,6 +307,10 @@ let
       TimeoutStartSec = 0;
       TimeoutStopSec = 120;
       Restart = "always";
+    } // optionalAttrs (cfg.backend == "podman") {
+      Environment="PODMAN_SYSTEMD_UNIT=podman-${name}.service";
+      Type="notify";
+      NotifyAccess="all";
     };
   };
 
@@ -338,18 +333,14 @@ in {
 
     backend = mkOption {
       type = types.enum [ "podman" "docker" ];
-      default =
-        # TODO: Once https://github.com/NixOS/nixpkgs/issues/77925 is resolved default to podman
-        # if versionAtLeast config.system.stateVersion "20.09" then "podman"
-        # else "docker";
-        "docker";
-      description = "The underlying Docker implementation to use.";
+      default = if versionAtLeast config.system.stateVersion "22.05" then "podman" else "docker";
+      description = lib.mdDoc "The underlying Docker implementation to use.";
     };
 
     containers = mkOption {
       default = {};
       type = types.attrsOf (types.submodule containerOptions);
-      description = "OCI (Docker) containers to run as systemd services.";
+      description = lib.mdDoc "OCI (Docker) containers to run as systemd services.";
     };
 
   };
diff --git a/nixos/modules/virtualisation/openstack-options.nix b/nixos/modules/virtualisation/openstack-options.nix
index cbc779f27c8f..c71b581b02ca 100644
--- a/nixos/modules/virtualisation/openstack-options.nix
+++ b/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
@@ -28,13 +28,13 @@ in
           type = types.attrsOf (types.submodule {
             options = {
               mount = lib.mkOption {
-                description = "Where to mount this dataset.";
+                description = lib.mdDoc "Where to mount this dataset.";
                 type = types.nullOr types.string;
                 default = null;
               };
 
               properties = lib.mkOption {
-                description = "Properties to set on this dataset.";
+                description = lib.mdDoc "Properties to set on this dataset.";
                 type = types.attrsOf types.string;
                 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/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix
index 436a375fb5eb..32646f60f8e0 100644
--- a/nixos/modules/virtualisation/openvswitch.nix
+++ b/nixos/modules/virtualisation/openvswitch.nix
@@ -13,7 +13,7 @@ in {
     enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to enable Open vSwitch. A configuration daemon (ovs-server)
         will be started.
         '';
@@ -22,9 +22,9 @@ in {
     resetOnStart = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to reset the Open vSwitch configuration database to a default
-        configuration on every start of the systemd <literal>ovsdb.service</literal>.
+        configuration on every start of the systemd `ovsdb.service`.
         '';
     };
 
@@ -32,7 +32,7 @@ in {
       type = types.package;
       default = pkgs.openvswitch;
       defaultText = literalExpression "pkgs.openvswitch";
-      description = ''
+      description = lib.mdDoc ''
         Open vSwitch package to use.
       '';
     };
diff --git a/nixos/modules/virtualisation/parallels-guest.nix b/nixos/modules/virtualisation/parallels-guest.nix
index d950cecff6f0..07a61bf208db 100644
--- a/nixos/modules/virtualisation/parallels-guest.nix
+++ b/nixos/modules/virtualisation/parallels-guest.nix
@@ -14,7 +14,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This enables Parallels Tools for Linux guests, along with provided
           video, mouse and other hardware drivers.
         '';
@@ -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
@@ -34,8 +34,9 @@ in
       package = mkOption {
         type = types.nullOr types.package;
         default = config.boot.kernelPackages.prl-tools;
-        defaultText = literalExpression "config.boot.kernelPackages.prl-tools";
-        description = ''
+        defaultText = "config.boot.kernelPackages.prl-tools";
+        example = literalExpression "config.boot.kernelPackages.prl-tools";
+        description = lib.mdDoc ''
           Defines which package to use for prl-tools. Override to change the version.
         '';
       };
@@ -44,27 +45,6 @@ in
   };
 
   config = mkIf config.hardware.parallels.enable {
-    services.xserver = {
-      drivers = singleton
-        { name = "prlvideo"; modules = [ prl-tools ]; };
-
-      screenSection = ''
-        Option "NoMTRR"
-      '';
-
-      config = ''
-        Section "InputClass"
-          Identifier "prlmouse"
-          MatchIsPointer "on"
-          MatchTag "prlmouse"
-          Driver "prlmouse"
-        EndSection
-      '';
-    };
-
-    hardware.opengl.package = prl-tools;
-    hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.prl-tools.override { libsOnly = true; kernel = null; };
-    hardware.opengl.setLdLibraryPath = true;
 
     services.udev.packages = [ prl-tools ];
 
@@ -72,37 +52,44 @@ in
 
     boot.extraModulePackages = [ prl-tools ];
 
-    boot.kernelModules = [ "prl_tg" "prl_eth" "prl_fs" "prl_fs_freeze" ];
+    boot.kernelModules = [ "prl_fs" "prl_fs_freeze" "prl_tg" ]
+      ++ optional (pkgs.stdenv.hostPlatform.system == "aarch64-linux") "prl_notifier";
 
     services.timesyncd.enable = false;
 
     systemd.services.prltoolsd = {
-      description = "Parallels Tools' service";
+      description = "Parallels Tools Service";
       wantedBy = [ "multi-user.target" ];
+      path = [ prl-tools ];
       serviceConfig = {
         ExecStart = "${prl-tools}/bin/prltoolsd -f";
         PIDFile = "/var/run/prltoolsd.pid";
+        WorkingDirectory = "${prl-tools}/bin";
       };
     };
 
     systemd.services.prlfsmountd = mkIf config.hardware.parallels.autoMountShares {
-      description = "Parallels Shared Folders Daemon";
+      description = "Parallels Guest File System Sharing Tool";
       wantedBy = [ "multi-user.target" ];
+      path = [ prl-tools ];
       serviceConfig = rec {
         ExecStart = "${prl-tools}/sbin/prlfsmountd ${PIDFile}";
         ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /media";
         ExecStopPost = "${prl-tools}/sbin/prlfsmountd -u";
         PIDFile = "/run/prlfsmountd.pid";
+        WorkingDirectory = "${prl-tools}/bin";
       };
     };
 
     systemd.services.prlshprint = {
-      description = "Parallels Shared Printer Tool";
+      description = "Parallels Printing Tool";
       wantedBy = [ "multi-user.target" ];
       bindsTo = [ "cups.service" ];
+      path = [ prl-tools ];
       serviceConfig = {
         Type = "forking";
         ExecStart = "${prl-tools}/bin/prlshprint";
+        WorkingDirectory = "${prl-tools}/bin";
       };
     };
 
@@ -110,43 +97,47 @@ in
       prlcc = {
         description = "Parallels Control Center";
         wantedBy = [ "graphical-session.target" ];
+        path = [ prl-tools ];
         serviceConfig = {
           ExecStart = "${prl-tools}/bin/prlcc";
+          WorkingDirectory = "${prl-tools}/bin";
         };
       };
       prldnd = {
-        description = "Parallels Control Center";
+        description = "Parallels Drag And Drop Tool";
         wantedBy = [ "graphical-session.target" ];
+        path = [ prl-tools ];
         serviceConfig = {
           ExecStart = "${prl-tools}/bin/prldnd";
-        };
-      };
-      prl_wmouse_d  = {
-        description = "Parallels Walking Mouse Daemon";
-        wantedBy = [ "graphical-session.target" ];
-        serviceConfig = {
-          ExecStart = "${prl-tools}/bin/prl_wmouse_d";
+          WorkingDirectory = "${prl-tools}/bin";
         };
       };
       prlcp = {
-        description = "Parallels CopyPaste Tool";
+        description = "Parallels Copy Paste Tool";
         wantedBy = [ "graphical-session.target" ];
+        path = [ prl-tools ];
         serviceConfig = {
           ExecStart = "${prl-tools}/bin/prlcp";
+          Restart = "always";
+          WorkingDirectory = "${prl-tools}/bin";
         };
       };
       prlsga = {
         description = "Parallels Shared Guest Applications Tool";
         wantedBy = [ "graphical-session.target" ];
+        path = [ prl-tools ];
         serviceConfig = {
           ExecStart = "${prl-tools}/bin/prlsga";
+          WorkingDirectory = "${prl-tools}/bin";
         };
       };
       prlshprof = {
         description = "Parallels Shared Profile Tool";
         wantedBy = [ "graphical-session.target" ];
+        path = [ prl-tools ];
         serviceConfig = {
           ExecStart = "${prl-tools}/bin/prlshprof";
+          WorkingDirectory = "${prl-tools}/bin";
         };
       };
     };
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index b7e7f78ded78..118bf82cdd66 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -12,10 +12,11 @@ let
   });
 
   # Provides a fake "docker" binary mapping to podman
-  dockerCompat = pkgs.runCommand "${podmanPackage.pname}-docker-compat-${podmanPackage.version}" {
-    outputs = [ "out" "man" ];
-    inherit (podmanPackage) meta;
-  } ''
+  dockerCompat = pkgs.runCommand "${podmanPackage.pname}-docker-compat-${podmanPackage.version}"
+    {
+      outputs = [ "out" "man" ];
+      inherit (podmanPackage) meta;
+    } ''
     mkdir -p $out/bin
     ln -s ${podmanPackage}/bin/podman $out/bin/docker
 
@@ -26,13 +27,14 @@ let
     done
   '';
 
-  net-conflist = pkgs.runCommand "87-podman-bridge.conflist" {
-    nativeBuildInputs = [ pkgs.jq ];
-    extraPlugins = builtins.toJSON cfg.defaultNetwork.extraPlugins;
-    jqScript = ''
-      . + { "plugins": (.plugins + $extraPlugins) }
-    '';
-  } ''
+  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" \
@@ -44,7 +46,6 @@ in
   imports = [
     ./dnsname.nix
     ./network-socket.nix
-    (lib.mkRenamedOptionModule [ "virtualisation" "podman" "libpod" ] [ "virtualisation" "containers" "containersConf" ])
   ];
 
   meta = {
@@ -57,24 +58,24 @@ in
       mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           This option enables Podman, a daemonless container engine for
           developing, managing, and running OCI Containers on your Linux System.
 
-          It is a drop-in replacement for the <command>docker</command> command.
+          It is a drop-in replacement for the {command}`docker` command.
         '';
       };
 
     dockerSocket.enable = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Make the Podman socket available in place of the Docker socket, so
         Docker tools can find the Podman socket.
 
         Podman implements the Docker API.
 
-        Users must be in the <code>podman</code> group in order to connect. As
+        Users must be in the `podman` group in order to connect. As
         with Docker, members of this group can gain root access.
       '';
     };
@@ -82,15 +83,15 @@ in
     dockerCompat = mkOption {
       type = types.bool;
       default = false;
-      description = ''
-        Create an alias mapping <command>docker</command> to <command>podman</command>.
+      description = lib.mdDoc ''
+        Create an alias mapping {command}`docker` to {command}`podman`.
       '';
     };
 
     enableNvidia = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Enable use of NVidia GPUs from within podman containers.
       '';
     };
@@ -103,7 +104,7 @@ in
           pkgs.gvisor
         ]
       '';
-      description = ''
+      description = lib.mdDoc ''
         Extra packages to be installed in the Podman wrapper.
       '';
     };
@@ -112,15 +113,15 @@ in
       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 = [];
-      description = ''
+      default = [ ];
+      description = lib.mdDoc ''
         Extra CNI plugin configurations to add to podman's default network.
       '';
     };
@@ -153,6 +154,12 @@ in
       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 = [
         # The /run/podman rule interferes with our podman group, so we remove
         # it and let the systemd socket logic take care of it.
@@ -161,14 +168,15 @@ in
           grep -v 'D! /run/podman 0700 root root' \
             <$package/lib/tmpfiles.d/podman.conf \
             >$out/lib/tmpfiles.d/podman.conf
-        '') ];
+        '')
+      ];
 
       systemd.tmpfiles.rules =
         lib.optionals cfg.dockerSocket.enable [
           "L! /run/docker.sock - - - - /run/podman/podman.sock"
         ];
 
-      users.groups.podman = {};
+      users.groups.podman = { };
 
       assertions = [
         {
diff --git a/nixos/modules/virtualisation/podman/dnsname.nix b/nixos/modules/virtualisation/podman/dnsname.nix
index beef19755079..3e7d35ae1e44 100644
--- a/nixos/modules/virtualisation/podman/dnsname.nix
+++ b/nixos/modules/virtualisation/podman/dnsname.nix
@@ -16,7 +16,7 @@ in
       defaultNetwork.dnsname.enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Enable DNS resolution in the default podman network.
         '';
       };
diff --git a/nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix b/nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix
index a0e7e433164a..ade4926c94cd 100644
--- a/nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix
+++ b/nixos/modules/virtualisation/podman/network-socket-ghostunnel.nix
@@ -26,7 +26,7 @@ in
         allowAll = lib.mkDefault true;
       };
     };
-    systemd.services.ghostunnel-server-podman-socket.serviceConfig.SupplementaryGroups = ["podman"];
+    systemd.services.ghostunnel-server-podman-socket.serviceConfig.SupplementaryGroups = [ "podman" ];
 
   };
 
diff --git a/nixos/modules/virtualisation/podman/network-socket.nix b/nixos/modules/virtualisation/podman/network-socket.nix
index 94d8da9d2b61..a10597175ab9 100644
--- a/nixos/modules/virtualisation/podman/network-socket.nix
+++ b/nixos/modules/virtualisation/podman/network-socket.nix
@@ -17,22 +17,22 @@ 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 <code>-H</code> and <code>--tls*</code> 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).
       '';
     };
 
     server = mkOption {
-      type = types.enum [];
-      description = ''
+      type = types.enum [ ];
+      description = lib.mdDoc ''
         Choice of TLS proxy server.
       '';
       example = "ghostunnel";
@@ -41,28 +41,28 @@ in
     openFirewall = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Whether to open the port in the firewall.
       '';
     };
 
     tls.cacert = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to CA certificate to use for client authentication.
       '';
     };
 
     tls.cert = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to certificate describing the server.
       '';
     };
 
     tls.key = mkOption {
       type = types.path;
-      description = ''
+      description = lib.mdDoc ''
         Path to the private key corresponding to the server certificate.
 
         Use a string for this setting. Otherwise it will be copied to the Nix
@@ -73,14 +73,14 @@ in
     port = mkOption {
       type = types.port;
       default = 2376;
-      description = ''
+      description = lib.mdDoc ''
         TCP port number for receiving TLS connections.
       '';
     };
     listenAddress = mkOption {
       type = types.str;
       default = "0.0.0.0";
-      description = ''
+      description = lib.mdDoc ''
         Interface address for receiving TLS connections.
       '';
     };
diff --git a/nixos/modules/virtualisation/proxmox-image.nix b/nixos/modules/virtualisation/proxmox-image.nix
index c537d5aed447..6a4220fd265c 100644
--- a/nixos/modules/virtualisation/proxmox-image.nix
+++ b/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
@@ -122,21 +140,70 @@ with lib;
       ${lib.concatStrings (lib.mapAttrsToList cfgLine properties)}
       #qmdump#map:virtio0:drive-virtio0:local-lvm: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: {
-          patches = let
-            rev = "cc707c362ea5c8d832aac270d1ffa7ac66a8908f";
-            path = "debian/patches/pve/0025-PVE-Backup-add-vma-backup-format-code.patch";
-            vma-patch = pkgs.fetchpatch {
-              url = "https://git.proxmox.com/?p=pve-qemu.git;a=blob_plain;hb=${rev};f=${path}";
-              sha256 = "1z467xnmfmry3pjy7p34psd5xdil9x0apnbvfz8qbj0bf9fgc8zf";
-            };
-          in super.patches ++ [ vma-patch ];
+        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 {
+
+          version = "7.0.0";
+          src = pkgs.fetchurl {
+            url= "https://download.qemu.org/qemu-${version}.tar.xz";
+            sha256 = "sha256-9rN1x5UfcoQCeYsLqrsthkeMpT1Eztvvq74cRr9G+Dk=";
+          };
+          patches = [
+            (pkgs.fetchpatch {
+              url =
+                let
+                  rev = "1976ca460796f28447b41e3618e5c1e234035dd5";
+                  path = "debian/patches/pve/0026-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-2Dz+ceTwrcyYYxi76RtyY3v15/2pwGcDhFuoZWlgbjc=";
+            })
+          ];
+
           buildInputs = super.buildInputs ++ [ pkgs.libuuid ];
+
         });
       in
       ''
@@ -145,6 +212,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;
@@ -153,7 +223,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" ];
     };
@@ -163,6 +244,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/nixos/modules/virtualisation/proxmox-lxc.nix b/nixos/modules/virtualisation/proxmox-lxc.nix
index 3913b474afbe..3d966d725a9a 100644
--- a/nixos/modules/virtualisation/proxmox-lxc.nix
+++ b/nixos/modules/virtualisation/proxmox-lxc.nix
@@ -7,19 +7,28 @@ 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.
       '';
     };
+    manageHostName = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to manage hostname through nix options
+        When false, the hostname is picked up from /etc/hostname
+        populated by proxmox.
+      '';
+    };
   };
 
   config =
@@ -50,6 +59,8 @@ with lib;
         useDHCP = false;
         useHostResolvConf = false;
         useNetworkd = true;
+        # pick up hostname from /etc/hostname generated by proxmox
+        hostName = mkIf (!cfg.manageHostName) (mkForce "");
       };
 
       services.openssh = {
diff --git a/nixos/modules/virtualisation/qemu-guest-agent.nix b/nixos/modules/virtualisation/qemu-guest-agent.nix
index 39273e523e8f..650fb2419160 100644
--- a/nixos/modules/virtualisation/qemu-guest-agent.nix
+++ b/nixos/modules/virtualisation/qemu-guest-agent.nix
@@ -10,13 +10,13 @@ in {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the qemu guest agent.";
+        description = lib.mdDoc "Whether to enable the qemu guest agent.";
       };
       package = mkOption {
         type = types.package;
         default = pkgs.qemu_kvm.ga;
         defaultText = literalExpression "pkgs.qemu_kvm.ga";
-        description = "The QEMU guest agent package.";
+        description = lib.mdDoc "The QEMU guest agent package.";
       };
   };
 
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index b1c5a7a6c95f..1b3c0e23f97d 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -17,6 +17,8 @@ let
 
   cfg = config.virtualisation;
 
+  opt = options.virtualisation;
+
   qemu = cfg.qemu.package;
 
   consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
@@ -27,26 +29,26 @@ let
 
       file = mkOption {
         type = types.str;
-        description = "The file image used for this drive.";
+        description = lib.mdDoc "The file image used for this drive.";
       };
 
       driveExtraOpts = mkOption {
         type = types.attrsOf types.str;
         default = {};
-        description = "Extra options passed to drive flag.";
+        description = lib.mdDoc "Extra options passed to drive flag.";
       };
 
       deviceExtraOpts = mkOption {
         type = types.attrsOf types.str;
         default = {};
-        description = "Extra options passed to device flag.";
+        description = lib.mdDoc "Extra options passed to device flag.";
       };
 
       name = mkOption {
         type = types.nullOr types.str;
         default = null;
         description =
-          "A name for the drive. Must be unique in the drives list. Not passed to qemu.";
+          lib.mdDoc "A name for the drive. Must be unique in the drives list. Not passed to qemu.";
       };
 
     };
@@ -96,17 +98,13 @@ 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}
+
+      export PATH=${makeBinPath [ cfg.host.pkgs.coreutils ]}''${PATH:+:}$PATH
 
       set -e
 
@@ -122,11 +120,32 @@ let
           TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir)
       fi
 
-      ${lib.optionalString cfg.useNixStoreImage
-      ''
-        # Create a writable copy/snapshot of the store image.
-        ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${storeImage}/nixos.qcow2 "$TMPDIR"/store.img
-      ''}
+      ${lib.optionalString (cfg.useNixStoreImage)
+        (if cfg.writableStore
+          then ''
+            # Create a writable copy/snapshot of the store image.
+            ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${storeImage}/nixos.qcow2 "$TMPDIR"/store.img
+          ''
+          else ''
+            (
+              cd ${builtins.storeDir}
+              ${pkgs.erofs-utils}/bin/mkfs.erofs \
+                --force-uid=0 \
+                --force-gid=0 \
+                -U eb176051-bd15-49b7-9e6b-462e0b467019 \
+                -T 0 \
+                --exclude-regex="$(
+                  <${pkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \
+                    sed -e 's^.*/^^g' \
+                  | cut -c -10 \
+                  | ${pkgs.python3}/bin/python ${./includes-to-excludes.py} )" \
+                "$TMPDIR"/store.img \
+                . \
+                </dev/null >/dev/null
+            )
+          ''
+        )
+      }
 
       # Create a directory for exchanging data with the VM.
       mkdir -p "$TMPDIR/xchg"
@@ -195,14 +214,14 @@ let
               ${qemu}/bin/qemu-img create -f qcow2 $diskImage "60M"
               ${if cfg.useEFIBoot then ''
                 efiVars=$out/efi-vars.fd
-                cp ${efiVarsDefault} $efiVars
+                cp ${cfg.efi.variables} $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=0,readonly=on,file=${cfg.efi.firmware}"
                       + " -drive if=pflash,format=raw,unit=1,file=$efiVars");
         }
         ''
@@ -298,7 +317,7 @@ in
         type = types.ints.positive;
         default = 1024;
         description =
-          ''
+          lib.mdDoc ''
             The memory size in megabytes of the virtual machine.
           '';
       };
@@ -308,7 +327,7 @@ in
         type = types.ints.positive;
         default = 16384;
         description =
-          ''
+          lib.mdDoc ''
             The msize (maximum packet size) option passed to 9p file systems, in
             bytes. Increasing this should increase performance significantly,
             at the cost of higher RAM usage.
@@ -320,7 +339,7 @@ in
         type = types.nullOr types.ints.positive;
         default = 1024;
         description =
-          ''
+          lib.mdDoc ''
             The disk size in megabytes of the virtual machine.
           '';
       };
@@ -331,7 +350,7 @@ in
         default = "./${config.system.name}.qcow2";
         defaultText = literalExpression ''"./''${config.system.name}.qcow2"'';
         description =
-          ''
+          lib.mdDoc ''
             Path to the disk image containing the root filesystem.
             The image will be created on startup if it does not
             exist.
@@ -343,7 +362,7 @@ in
         type = types.path;
         example = "/dev/vda";
         description =
-          ''
+          lib.mdDoc ''
             The disk to be used for the root filesystem.
           '';
       };
@@ -353,7 +372,7 @@ in
         type = types.listOf types.ints.positive;
         default = [];
         description =
-          ''
+          lib.mdDoc ''
             Additional disk images to provide to the VM. The value is
             a list of size in megabytes of each disk. These disks are
             writeable by the VM.
@@ -365,7 +384,7 @@ in
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             Whether to run QEMU with a graphics window, or in nographic mode.
             Serial console will be enabled on both settings, but this will
             change the preferred console.
@@ -377,7 +396,7 @@ in
         type = options.services.xserver.resolutions.type.nestedTypes.elemType;
         default = { x = 1024; y = 768; };
         description =
-          ''
+          lib.mdDoc ''
             The resolution of the virtual machine display.
           '';
       };
@@ -387,7 +406,7 @@ in
         type = types.ints.positive;
         default = 1;
         description =
-          ''
+          lib.mdDoc ''
             Specify the number of cores the guest is permitted to use.
             The number can be higher than the available cores on the
             host system.
@@ -400,11 +419,11 @@ in
           (types.submodule {
             options.source = mkOption {
               type = types.str;
-              description = "The path of the directory to share, can be a shell variable";
+              description = lib.mdDoc "The path of the directory to share, can be a shell variable";
             };
             options.target = mkOption {
               type = types.path;
-              description = "The mount point of the directory inside the virtual machine";
+              description = lib.mdDoc "The mount point of the directory inside the virtual machine";
             };
           });
         default = { };
@@ -412,7 +431,7 @@ in
           my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; };
         };
         description =
-          ''
+          lib.mdDoc ''
             An attributes set of directories that will be shared with the
             virtual machine using VirtFS (9P filesystem over VirtIO).
             The attribute name will be used as the 9P mount tag.
@@ -424,7 +443,7 @@ in
         type = types.listOf types.path;
         default = [];
         description =
-          ''
+          lib.mdDoc ''
             A list of paths whose closure should be made available to
             the VM.
 
@@ -434,7 +453,7 @@ in
             garbage (because they are not registered in the Nix
             database of the guest).
 
-            When <option>virtualisation.useNixStoreImage</option> is
+            When {option}`virtualisation.useNixStoreImage` is
             set, the closure is copied to the Nix store image.
           '';
       };
@@ -446,38 +465,37 @@ 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 {
             type = types.enum [ "tcp" "udp" ];
             default = "tcp";
-            description = "The protocol to forward.";
+            description = lib.mdDoc "The protocol to forward.";
           };
           options.host.address = mkOption {
             type = types.str;
             default = "";
-            description = "The IPv4 address of the host.";
+            description = lib.mdDoc "The IPv4 address of the host.";
           };
           options.host.port = mkOption {
             type = types.port;
-            description = "The host port to be mapped.";
+            description = lib.mdDoc "The host port to be mapped.";
           };
           options.guest.address = mkOption {
             type = types.str;
             default = "";
-            description = "The IPv4 address on the guest VLAN.";
+            description = lib.mdDoc "The IPv4 address on the guest VLAN.";
           };
           options.guest.port = mkOption {
             type = types.port;
-            description = "The guest port to be mapped.";
+            description = lib.mdDoc "The guest port to be mapped.";
           };
         });
       default = [];
@@ -494,17 +512,19 @@ 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.
+          :::
         '';
     };
 
@@ -514,14 +534,14 @@ in
         default = [ 1 ];
         example = [ 1 2 ];
         description =
-          ''
+          lib.mdDoc ''
             Virtual networks to which the VM is connected.  Each
-            number <replaceable>N</replaceable> in this list causes
+            number «N» in this list causes
             the VM to have a virtual Ethernet interface attached to a
             separate virtual network on which it will be assigned IP
             address
-            <literal>192.168.<replaceable>N</replaceable>.<replaceable>M</replaceable></literal>,
-            where <replaceable>M</replaceable> is the index of this VM
+            `192.168.«N».«M»`,
+            where «M» is the index of this VM
             in the list of VMs.
           '';
       };
@@ -531,7 +551,7 @@ in
         type = types.bool;
         default = true; # FIXME
         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.
@@ -543,7 +563,7 @@ in
         type = types.bool;
         default = true;
         description =
-          ''
+          lib.mdDoc ''
             Use a tmpfs for the writable store instead of writing to the VM's
             own filesystem.
           '';
@@ -554,16 +574,30 @@ 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";
-          description = "QEMU package to use.";
+          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.";
         };
 
       options =
@@ -571,7 +605,7 @@ in
           type = types.listOf types.str;
           default = [];
           example = [ "-vga std" ];
-          description = "Options passed to QEMU.";
+          description = lib.mdDoc "Options passed to QEMU.";
         };
 
       consoles = mkOption {
@@ -580,14 +614,14 @@ in
           consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ];
         in if cfg.graphics then consoles else reverseList consoles;
         example = [ "console=tty1" ];
-        description = ''
+        description = lib.mdDoc ''
           The output console devices to pass to the kernel command line via the
-          <literal>console</literal> parameter, the primary console is the last
+          `console` parameter, the primary console is the last
           item of this list.
 
           By default it enables both serial console and
-          <literal>tty0</literal>. The preferred console (last one) is based on
-          the value of <option>virtualisation.graphics</option>.
+          `tty0`. The preferred console (last one) is based on
+          the value of {option}`virtualisation.graphics`.
         '';
       };
 
@@ -599,7 +633,7 @@ in
             "-net nic,netdev=user.0,model=virtio"
             "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
           ];
-          description = ''
+          description = lib.mdDoc ''
             Networking-related command-line options that should be passed to qemu.
             The default is to use userspace networking (SLiRP).
 
@@ -612,7 +646,7 @@ in
       drives =
         mkOption {
           type = types.listOf (types.submodule driveOpts);
-          description = "Drives passed to qemu.";
+          description = lib.mdDoc "Drives passed to qemu.";
           apply = addDeviceNames;
         };
 
@@ -621,14 +655,14 @@ in
           type = types.enum [ "virtio" "scsi" "ide" ];
           default = "virtio";
           example = "scsi";
-          description = "The interface used for the virtual hard disks.";
+          description = lib.mdDoc "The interface used for the virtual hard disks.";
         };
 
       guestAgent.enable =
         mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Enable the Qemu guest agent.
           '';
         };
@@ -637,7 +671,7 @@ in
         mkOption {
           type = types.bool;
           default = true;
-          description = ''
+          description = lib.mdDoc ''
             Enable the virtio-keyboard device.
           '';
         };
@@ -647,7 +681,7 @@ in
       mkOption {
         type = types.bool;
         default = false;
-        description = ''
+        description = lib.mdDoc ''
           Build and use a disk image for the Nix store, instead of
           accessing the host's one through 9p.
 
@@ -662,7 +696,7 @@ in
         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
@@ -677,11 +711,49 @@ in
         type = types.bool;
         default = false;
         description =
-          ''
+          lib.mdDoc ''
             If enabled, the virtual machine will provide a EFI boot
             manager.
             useEFIBoot is ignored if useBootLoader == false.
           '';
+        };
+
+    virtualisation.efi = {
+      firmware = mkOption {
+        type = types.path;
+        default = pkgs.OVMF.firmware;
+        defaultText = literalExpression "pkgs.OVMF.firmware";
+        description =
+          lib.mdDoc ''
+            Firmware binary for EFI implementation, defaults to OVMF.
+          '';
+      };
+
+      variables = mkOption {
+        type = types.path;
+        default = pkgs.OVMF.variables;
+        defaultText = literalExpression "pkgs.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;
+        default = true;
+        description =
+          lib.mdDoc ''
+            If enabled, the boot disk of the virtual machine will be
+            formatted and mounted with the default filesystems for
+            testing. Swap devices and LUKS will be disabled.
+
+            If disabled, a root filesystem has to be specified and
+            formatted (for example in the initial ramdisk).
+          '';
       };
 
     virtualisation.efiVars =
@@ -690,7 +762,7 @@ in
         default = "./${config.system.name}-efi-vars.fd";
         defaultText = literalExpression ''"./''${config.system.name}-efi-vars.fd"'';
         description =
-          ''
+          lib.mdDoc ''
             Path to nvram image containing UEFI variables.  The will be created
             on startup if it does not exist.
           '';
@@ -701,10 +773,10 @@ in
         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.
           '';
       };
 
@@ -731,6 +803,26 @@ in
           }
         ]));
 
+    warnings =
+      optional (
+        cfg.writableStore &&
+        cfg.useNixStoreImage &&
+        opt.writableStore.highestPrio > lib.modules.defaultOverridePriority)
+        ''
+          You have enabled ${opt.useNixStoreImage} = true,
+          without setting ${opt.writableStore} = false.
+
+          This causes a store image to be written to the store, which is
+          costly, especially for the binary cache, and because of the need
+          for more frequent garbage collection.
+
+          If you really need this combination, you can set ${opt.writableStore}
+          explicitly to true, incur the cost and make this warning go away.
+          Otherwise, we recommend
+
+            ${opt.writableStore} = false;
+        '';
+
     # Note [Disk layout with `useBootLoader`]
     #
     # If `useBootLoader = true`, we configure 2 drives:
@@ -754,23 +846,26 @@ in
     );
     boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
 
-    boot.initrd.extraUtilsCommands =
+    boot.initrd.kernelModules = optionals (cfg.useNixStoreImage && !cfg.writableStore) [ "erofs" ];
+
+    boot.initrd.extraUtilsCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
       ''
         # We need mke2fs in the initrd.
         copy_bin_and_libs ${pkgs.e2fsprogs}/bin/mke2fs
       '';
 
-    boot.initrd.postDeviceCommands =
+    boot.initrd.postDeviceCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
       ''
         # 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
+        PARTTYPE=$(blkid -o value -s PTTYPE ${cfg.bootDevice} || true)
+        if test -z "$FSTYPE" -a -z "$PARTTYPE"; then
             mke2fs -t ext4 ${cfg.bootDevice}
         fi
       '';
 
-    boot.initrd.postMountCommands =
+    boot.initrd.postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
       ''
         # Mark this as a NixOS machine.
         mkdir -p $targetRoot/etc
@@ -789,6 +884,11 @@ in
         ''}
       '';
 
+    systemd.tmpfiles.rules = lib.mkIf config.boot.initrd.systemd.enable [
+      "f /etc/NIXOS 0644 root root -"
+      "d /boot 0644 root root -"
+    ];
+
     # After booting, register the closure of the paths in
     # `virtualisation.additionalPaths' in the Nix database in the VM.  This
     # allows Nix operations to work in the VM.  The path to the
@@ -850,7 +950,7 @@ in
       (mkIf pkgs.stdenv.hostPlatform.isx86 [
         "-usb" "-device usb-tablet,bus=usb-bus.0"
       ])
-      (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
+      (mkIf pkgs.stdenv.hostPlatform.isAarch [
         "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
       ])
       (let
@@ -863,7 +963,7 @@ in
         ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
       ])
       (mkIf cfg.useEFIBoot [
-        "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
+        "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}"
         "-drive if=pflash,format=raw,unit=1,file=$NIX_EFI_VARS"
       ])
       (mkIf (cfg.bios != null) [
@@ -885,6 +985,7 @@ in
         name = "nix-store";
         file = ''"$TMPDIR"/store.img'';
         deviceExtraOpts.bootindex = if cfg.useBootLoader then "3" else "2";
+        driveExtraOpts.format = if cfg.writableStore then "qcow2" else "raw";
       }])
       (mkIf cfg.useBootLoader [
         # The order of this list determines the device names, see
@@ -925,38 +1026,41 @@ in
         };
     in
       mkVMOverride (cfg.fileSystems //
-      {
+      optionalAttrs cfg.useDefaultFilesystems {
         "/".device = cfg.bootDevice;
         "/".fsType = "ext4";
         "/".autoFormat = true;
-
-        "/tmp" = mkIf config.boot.tmpOnTmpfs
-          { device = "tmpfs";
-            fsType = "tmpfs";
-            neededForBoot = true;
-            # Sync with systemd's tmp.mount;
-            options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
-          };
-
-        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" =
-          mkIf cfg.useNixStoreImage
-            { device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
-              neededForBoot = true;
-              options = [ "ro" ];
-            };
-
-        "/nix/.rw-store" = mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs)
-          { fsType = "tmpfs";
-            options = [ "mode=0755" ];
-            neededForBoot = true;
-          };
-
-        "/boot" = mkIf cfg.useBootLoader
-          # see note [Disk layout with `useBootLoader`]
-          { device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
-            fsType = "vfat";
-            noCheck = true; # fsck fails on a r/o filesystem
-          };
+      } //
+      optionalAttrs config.boot.tmpOnTmpfs {
+        "/tmp" = {
+          device = "tmpfs";
+          fsType = "tmpfs";
+          neededForBoot = true;
+          # Sync with systemd's tmp.mount;
+          options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ];
+        };
+      } //
+      optionalAttrs cfg.useNixStoreImage {
+        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = {
+          device = "${lookupDriveDeviceName "nix-store" cfg.qemu.drives}";
+          neededForBoot = true;
+          options = [ "ro" ];
+        };
+      } //
+      optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs) {
+        "/nix/.rw-store" = {
+          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`
+          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) {
@@ -981,22 +1085,22 @@ in
       };
     };
 
-    swapDevices = mkVMOverride [ ];
-    boot.initrd.luks.devices = mkVMOverride {};
+    swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ];
+    boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {};
 
     # Don't run ntpd in the guest.  It should get the correct time from KVM.
     services.timesyncd.enable = false;
 
     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/nixos/modules/virtualisation/railcar.nix b/nixos/modules/virtualisation/railcar.nix
deleted file mode 100644
index e719e25650d3..000000000000
--- a/nixos/modules/virtualisation/railcar.nix
+++ /dev/null
@@ -1,124 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.railcar;
-  generateUnit = name: containerConfig:
-    let
-      container = pkgs.ociTools.buildContainer {
-        args = [
-          (pkgs.writeShellScript "run.sh" containerConfig.cmd).outPath
-        ];
-      };
-    in
-      nameValuePair "railcar-${name}" {
-        enable = true;
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-            ExecStart = ''
-              ${cfg.package}/bin/railcar -r ${cfg.stateDir} run ${name} -b ${container}
-            '';
-            Type = containerConfig.runType;
-          };
-      };
-  mount = with types; (submodule {
-    options = {
-      type = mkOption {
-        type = str;
-        default = "none";
-        description = ''
-          The type of the filesystem to be mounted.
-          Linux: filesystem types supported by the kernel as listed in
-          `/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs",
-          "reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts
-          (when options include either bind or rbind), the type is a dummy,
-          often "none" (not listed in /proc/filesystems).
-        '';
-      };
-      source = mkOption {
-        type = str;
-        description = "Source for the in-container mount";
-      };
-      options = mkOption {
-        type = listOf str;
-        default = [ "bind" ];
-        description = ''
-          Mount options of the filesystem to be used.
-
-          Support options are listed in the mount(8) man page. Note that
-          both filesystem-independent and filesystem-specific options
-          are listed.
-        '';
-      };
-    };
-  });
-in
-{
-  options.services.railcar = {
-    enable = mkEnableOption "railcar";
-
-    containers = mkOption {
-      default = {};
-      description = "Declarative container configuration";
-      type = with types; attrsOf (submodule ({ name, config, ... }: {
-        options = {
-          cmd = mkOption {
-            type = types.lines;
-            description = "Command or script to run inside the container";
-          };
-
-          mounts = mkOption {
-            type = with types; attrsOf mount;
-            default = {};
-            description = ''
-              A set of mounts inside the container.
-
-              The defaults have been chosen for simple bindmounts, meaning
-              that you only need to provide the "source" parameter.
-            '';
-            example = { "/data" = { source = "/var/lib/data"; }; };
-          };
-
-          runType = mkOption {
-            type = types.str;
-            default = "oneshot";
-            description = "The systemd service run type";
-          };
-
-          os = mkOption {
-            type = types.str;
-            default = "linux";
-            description = "OS type of the container";
-          };
-
-          arch = mkOption {
-            type = types.str;
-            default = "x86_64";
-            description = "Computer architecture type of the container";
-          };
-        };
-      }));
-    };
-
-    stateDir = mkOption {
-      type = types.path;
-      default = "/var/railcar";
-      description = "Railcar persistent state directory";
-    };
-
-    package = mkOption {
-      type = types.package;
-      default = pkgs.railcar;
-      defaultText = literalExpression "pkgs.railcar";
-      description = "Railcar package to use";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services = flip mapAttrs' cfg.containers (name: containerConfig:
-      generateUnit name containerConfig
-    );
-  };
-}
-
diff --git a/nixos/modules/virtualisation/rosetta.nix b/nixos/modules/virtualisation/rosetta.nix
new file mode 100644
index 000000000000..109b114d649c
--- /dev/null
+++ b/nixos/modules/virtualisation/rosetta.nix
@@ -0,0 +1,73 @@
+{ 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";
+    };
+
+    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/nixos/modules/virtualisation/spice-usb-redirection.nix b/nixos/modules/virtualisation/spice-usb-redirection.nix
index 255327f2622c..ab2b058c686f 100644
--- a/nixos/modules/virtualisation/spice-usb-redirection.nix
+++ b/nixos/modules/virtualisation/spice-usb-redirection.nix
@@ -3,7 +3,7 @@
   options.virtualisation.spiceUSBRedirection.enable = lib.mkOption {
     type = lib.types.bool;
     default = false;
-    description = ''
+    description = lib.mdDoc ''
       Install the SPICE USB redirection helper with setuid
       privileges. This allows unprivileged users to pass USB devices
       connected to this machine to libvirt VMs, both local and
diff --git a/nixos/modules/virtualisation/virtualbox-guest.nix b/nixos/modules/virtualisation/virtualbox-guest.nix
index 7b55b3b9759e..94f70c65436c 100644
--- a/nixos/modules/virtualisation/virtualbox-guest.nix
+++ b/nixos/modules/virtualisation/virtualbox-guest.nix
@@ -19,13 +19,13 @@ in
     enable = mkOption {
       default = false;
       type = types.bool;
-      description = "Whether to enable the VirtualBox service and other guest additions.";
+      description = lib.mdDoc "Whether to enable the VirtualBox service and other guest additions.";
     };
 
     x11 = mkOption {
       default = true;
       type = types.bool;
-      description = "Whether to enable x11 graphics";
+      description = lib.mdDoc "Whether to enable x11 graphics";
     };
   };
 
diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix
index 2acf54aae2ef..b1565a09682a 100644
--- a/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/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.
+        :::
       '';
     };
 
@@ -44,7 +44,7 @@ in
       type = types.package;
       default = pkgs.virtualbox;
       defaultText = literalExpression "pkgs.virtualbox";
-      description = ''
+      description = lib.mdDoc ''
         Which VirtualBox package to use.
       '';
     };
@@ -52,7 +52,7 @@ in
     addNetworkInterface = mkOption {
       type = types.bool;
       default = true;
-      description = ''
+      description = lib.mdDoc ''
         Automatically set up a vboxnet0 host-only network interface.
       '';
     };
@@ -60,22 +60,22 @@ 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.
+        :::
       '';
     };
 
     headless = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Use VirtualBox installation without GUI and Qt dependency. Useful to enable on servers
         and when virtual machines are controlled only via SSH.
       '';
@@ -84,7 +84,7 @@ in
     enableWebService = mkOption {
       type = types.bool;
       default = false;
-      description = ''
+      description = lib.mdDoc ''
         Build VirtualBox web service tool (vboxwebsrv) to allow managing VMs via other webpage frontend tools. Useful for headless servers.
       '';
     };
@@ -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/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index 1a0c4df42cb3..0c095c01ad80 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -14,42 +14,42 @@ in {
         type = with types; either (enum [ "auto" ]) int;
         default = "auto";
         example = 50 * 1024;
-        description = ''
+        description = lib.mdDoc ''
           The size of the VirtualBox base image in MiB.
         '';
       };
       baseImageFreeSpace = mkOption {
         type = with types; int;
         default = 30 * 1024;
-        description = ''
+        description = lib.mdDoc ''
           Free space in the VirtualBox base image in MiB.
         '';
       };
       memorySize = mkOption {
         type = types.int;
         default = 1536;
-        description = ''
+        description = lib.mdDoc ''
           The amount of RAM the VirtualBox appliance can use in MiB.
         '';
       };
       vmDerivationName = mkOption {
         type = types.str;
         default = "nixos-ova-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
-        description = ''
+        description = lib.mdDoc ''
           The name of the derivation for the VirtualBox appliance.
         '';
       };
       vmName = mkOption {
         type = types.str;
         default = "NixOS ${config.system.nixos.label} (${pkgs.stdenv.hostPlatform.system})";
-        description = ''
+        description = lib.mdDoc ''
           The name of the VirtualBox appliance.
         '';
       };
       vmFileName = mkOption {
         type = types.str;
         default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.ova";
-        description = ''
+        description = lib.mdDoc ''
           The file name of the VirtualBox appliance.
         '';
       };
@@ -60,10 +60,10 @@ in {
           rtcuseutc = "on";
           usb = "off";
         };
-        description = ''
+        description = lib.mdDoc ''
           Parameters passed to the Virtualbox appliance.
 
-          Run <literal>VBoxManage modifyvm --help</literal> to see more options.
+          Run `VBoxManage modifyvm --help` to see more options.
         '';
       };
       exportParams = mkOption {
@@ -72,14 +72,14 @@ in {
           "--vsys" "0" "--vendor" "ACME Inc."
         ];
         default = [];
-        description = ''
+        description = lib.mdDoc ''
           Parameters passed to the Virtualbox export command.
 
-          Run <literal>VBoxManage export --help</literal> to see more options.
+          Run `VBoxManage export --help` to see more options.
         '';
       };
       extraDisk = mkOption {
-        description = ''
+        description = lib.mdDoc ''
           Optional extra disk/hdd configuration.
           The disk will be an 'ext4' partition on a separate VMDK file.
         '';
@@ -93,16 +93,16 @@ in {
           options = {
             size = mkOption {
               type = types.int;
-              description = "Size in MiB";
+              description = lib.mdDoc "Size in MiB";
             };
             label = mkOption {
               type = types.str;
               default = "vm-extra-storage";
-              description = "Label for the disk partition";
+              description = lib.mdDoc "Label for the disk partition";
             };
             mountPoint = mkOption {
               type = types.str;
-              description = "Path where to mount this disk.";
+              description = lib.mdDoc "Path where to mount this disk.";
             };
           };
         });
diff --git a/nixos/modules/virtualisation/vmware-guest.nix b/nixos/modules/virtualisation/vmware-guest.nix
index 3caed746ca91..b8f0a4cf668e 100644
--- a/nixos/modules/virtualisation/vmware-guest.nix
+++ b/nixos/modules/virtualisation/vmware-guest.nix
@@ -13,11 +13,12 @@ in
   ];
 
   options.virtualisation.vmware.guest = {
-    enable = mkEnableOption "VMWare Guest Support";
+    enable = mkEnableOption (lib.mdDoc "VMWare Guest Support");
     headless = mkOption {
       type = types.bool;
-      default = false;
-      description = "Whether to disable X11-related features.";
+      default = !config.services.xserver.enable;
+      defaultText = "!config.services.xserver.enable";
+      description = lib.mdDoc "Whether to disable X11-related features.";
     };
   };
 
@@ -64,7 +65,6 @@ in
     environment.etc.vmware-tools.source = "${open-vm-tools}/etc/vmware-tools/*";
 
     services.xserver = mkIf (!cfg.headless) {
-      videoDrivers = mkOverride 50 [ "vmware" ];
       modules = [ xf86inputvmmouse ];
 
       config = ''
diff --git a/nixos/modules/virtualisation/vmware-host.nix b/nixos/modules/virtualisation/vmware-host.nix
new file mode 100644
index 000000000000..4b2dc28aeac7
--- /dev/null
+++ b/nixos/modules/virtualisation/vmware-host.nix
@@ -0,0 +1,166 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.virtualisation.vmware.host;
+  wrapperDir = "/run/vmware/bin"; # Perfectly fits as /usr/local/bin
+  parentWrapperDir = dirOf wrapperDir;
+  vmwareWrappers = # Needed as hardcoded paths workaround
+    let mkVmwareSymlink =
+      program:
+      ''
+        ln -s "${config.security.wrapperDir}/${program}" $wrapperDir/${program}
+      '';
+    in
+    [
+      (mkVmwareSymlink "pkexec")
+      (mkVmwareSymlink "mount")
+      (mkVmwareSymlink "umount")
+    ];
+in
+{
+  options = with lib; {
+    virtualisation.vmware.host = {
+      enable = mkEnableOption (lib.mdDoc "VMware") // {
+        description = lib.mdDoc ''
+          This enables VMware host virtualisation for running VMs.
+
+          ::: {.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 {
+        type = types.package;
+        default = pkgs.vmware-workstation;
+        defaultText = literalExpression "pkgs.vmware-workstation";
+        description = lib.mdDoc "VMware host virtualisation package to use";
+      };
+      extraPackages = mkOption {
+        type = with types; listOf package;
+        default = with pkgs; [ ];
+        description = lib.mdDoc "Extra packages to be used with VMware host.";
+        example = "with pkgs; [ ntfs3g ]";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = lib.mdDoc "Add extra config to /etc/vmware/config";
+        example = ''
+          # Allow unsupported device's OpenGL and Vulkan acceleration for guest vGPU
+          mks.gl.allowUnsupportedDrivers = "TRUE"
+          mks.vk.allowUnsupportedDevices = "TRUE"
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    boot.extraModulePackages = [ config.boot.kernelPackages.vmware ];
+    boot.extraModprobeConfig = "alias char-major-10-229 fuse";
+    boot.kernelModules = [ "vmw_pvscsi" "vmw_vmci" "vmmon" "vmnet" "fuse" ];
+
+    environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
+    services.printing.drivers = [ cfg.package ];
+
+    environment.etc."vmware/config".text = ''
+      ${builtins.readFile "${cfg.package}/etc/vmware/config"}
+      ${cfg.extraConfig}
+    '';
+
+    environment.etc."vmware/bootstrap".source = "${cfg.package}/etc/vmware/bootstrap";
+    environment.etc."vmware/icu".source = "${cfg.package}/etc/vmware/icu";
+    environment.etc."vmware-installer".source = "${cfg.package}/etc/vmware-installer";
+
+    # SUID wrappers
+
+    security.wrappers = {
+      vmware-vmx = {
+        setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${cfg.package}/lib/vmware/bin/.vmware-vmx-wrapped";
+      };
+    };
+
+    ###### wrappers activation script
+
+    system.activationScripts.vmwareWrappers =
+      lib.stringAfter [ "specialfs" "users" ]
+        ''
+          mkdir -p "${parentWrapperDir}"
+          chmod 755 "${parentWrapperDir}"
+          # We want to place the tmpdirs for the wrappers to the parent dir.
+          wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
+          chmod a+rx "$wrapperDir"
+          ${lib.concatStringsSep "\n" (vmwareWrappers)}
+          if [ -L ${wrapperDir} ]; then
+            # Atomically replace the symlink
+            # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
+            old=$(readlink -f ${wrapperDir})
+            if [ -e "${wrapperDir}-tmp" ]; then
+              rm --force --recursive "${wrapperDir}-tmp"
+            fi
+            ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
+            mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
+            rm --force --recursive "$old"
+          else
+            # For initial setup
+            ln --symbolic "$wrapperDir" "${wrapperDir}"
+          fi
+        '';
+
+    # Services
+
+    systemd.services."vmware-authdlauncher" = {
+      description = "VMware Authentication Daemon";
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = [ "${cfg.package}/bin/vmware-authdlauncher" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services."vmware-networks-configuration" = {
+      description = "VMware Networks Configuration Generation";
+      unitConfig.ConditionPathExists = "!/etc/vmware/networking";
+      serviceConfig = {
+        UMask = "0077";
+        ExecStart = [
+          "${cfg.package}/bin/vmware-networks --postinstall vmware-player,0,1"
+        ];
+        Type = "oneshot";
+        RemainAfterExit = "yes";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services."vmware-networks" = {
+      description = "VMware Networks";
+      after = [ "vmware-networks-configuration.service" ];
+      requires = [ "vmware-networks-configuration.service" ];
+      serviceConfig = {
+        Type = "forking";
+        ExecCondition = [ "${pkgs.kmod}/bin/modprobe vmnet" ];
+        ExecStart = [ "${cfg.package}/bin/vmware-networks --start" ];
+        ExecStop = [ "${cfg.package}/bin/vmware-networks --stop" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services."vmware-usbarbitrator" = {
+      description = "VMware USB Arbitrator";
+      serviceConfig = {
+        ExecStart = [ "${cfg.package}/bin/vmware-usbarbitrator -f" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/vmware-image.nix b/nixos/modules/virtualisation/vmware-image.nix
index f6cd12e2bb79..a38713b4d4ee 100644
--- a/nixos/modules/virtualisation/vmware-image.nix
+++ b/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/nixos/modules/virtualisation/waydroid.nix b/nixos/modules/virtualisation/waydroid.nix
index 2c0b658948dd..a2cfd806f322 100644
--- a/nixos/modules/virtualisation/waydroid.nix
+++ b/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 {
@@ -34,7 +34,7 @@ in
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isEnabled "ANDROID_BINDER_IPC")
       (isEnabled "ANDROID_BINDERFS")
-      (isEnabled "ASHMEM")
+      (isEnabled "ASHMEM") # FIXME Needs memfd support instead on Linux 5.18 and waydroid 1.2.1
     ];
 
     /* NOTE: we always enable this flag even if CONFIG_PSI_DEFAULT_DISABLED is not on
diff --git a/nixos/modules/virtualisation/xe-guest-utilities.nix b/nixos/modules/virtualisation/xe-guest-utilities.nix
index 25ccbaebc077..792edc9b397d 100644
--- a/nixos/modules/virtualisation/xe-guest-utilities.nix
+++ b/nixos/modules/virtualisation/xe-guest-utilities.nix
@@ -5,7 +5,7 @@ let
 in {
   options = {
     services.xe-guest-utilities = {
-      enable = mkEnableOption "the Xen guest utilities daemon";
+      enable = mkEnableOption (lib.mdDoc "the Xen guest utilities daemon");
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix
index 975eed10cd26..8f361a7ac020 100644
--- a/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixos/modules/virtualisation/xen-dom0.nix
@@ -23,12 +23,12 @@ in
         default = false;
         type = types.bool;
         description =
-          ''
+          mdDoc ''
             Setting this option enables the Xen hypervisor, a
             virtualisation technology that allows multiple virtual
-            machines, known as <emphasis>domains</emphasis>, to run
+            machines, known as *domains*, to run
             concurrently on the physical machine.  NixOS runs as the
-            privileged <emphasis>Domain 0</emphasis>.  This option
+            privileged *Domain 0*.  This option
             requires a reboot to take effect.
           '';
       };
@@ -37,7 +37,7 @@ in
       type = types.package;
       defaultText = literalExpression "pkgs.xen";
       example = literalExpression "pkgs.xen-light";
-      description = ''
+      description = lib.mdDoc ''
         The package used for Xen binary.
       '';
       relatedPackages = [ "xen" "xen-light" ];
@@ -47,7 +47,7 @@ in
       type = types.package;
       defaultText = literalExpression "pkgs.xen";
       example = literalExpression "pkgs.qemu_xen-light";
-      description = ''
+      description = lib.mdDoc ''
         The package with qemu binaries for dom0 qemu and xendomains.
       '';
       relatedPackages = [ "xen"
@@ -59,7 +59,7 @@ in
       mkOption {
         default = [];
         type = types.listOf types.str;
-        description =
+        description = lib.mdDoc
           ''
             Parameters passed to the Xen hypervisor at boot time.
           '';
@@ -70,7 +70,7 @@ in
         default = 0;
         example = 512;
         type = types.addCheck types.int (n: n >= 0);
-        description =
+        description = lib.mdDoc
           ''
             Amount of memory (in MiB) allocated to Domain 0 on boot.
             If set to 0, all memory is assigned to Domain 0.
@@ -81,7 +81,7 @@ in
         name = mkOption {
           default = "xenbr0";
           type = types.str;
-          description = ''
+          description = lib.mdDoc ''
               Name of bridge the Xen domUs connect to.
             '';
         };
@@ -89,7 +89,7 @@ in
         address = mkOption {
           type = types.str;
           default = "172.16.0.1";
-          description = ''
+          description = lib.mdDoc ''
             IPv4 address of the bridge.
           '';
         };
@@ -97,9 +97,9 @@ in
         prefixLength = mkOption {
           type = types.addCheck types.int (n: n >= 0 && n <= 32);
           default = 16;
-          description = ''
+          description = lib.mdDoc ''
             Subnet mask of the bridge interface, specified as the number of
-            bits in the prefix (<literal>24</literal>).
+            bits in the prefix (`24`).
             A DHCP server will provide IP addresses for the whole, remaining
             subnet.
           '';
@@ -108,8 +108,8 @@ in
         forwardDns = mkOption {
           type = types.bool;
           default = false;
-          description = ''
-            If set to <literal>true</literal>, the DNS queries from the
+          description = lib.mdDoc ''
+            If set to `true`, the DNS queries from the
             hosts connected to the bridge will be forwarded to the DNS
             servers specified in /etc/resolv.conf .
             '';
@@ -120,7 +120,7 @@ in
     virtualisation.xen.stored =
       mkOption {
         type = types.path;
-        description =
+        description = lib.mdDoc
           ''
             Xen Store daemon to use. Defaults to oxenstored of the xen package.
           '';
@@ -130,7 +130,7 @@ in
         extraConfig = mkOption {
           type = types.lines;
           default = "";
-          description =
+          description = lib.mdDoc
             ''
               Options defined here will override the defaults for xendomains.
               The default options can be seen in the file included from
@@ -139,7 +139,7 @@ in
           };
       };
 
-    virtualisation.xen.trace = mkEnableOption "Xen tracing";
+    virtualisation.xen.trace = mkEnableOption (lib.mdDoc "Xen tracing");
 
   };
 
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index fd8a39cfb92b..337b5192776f 100644
--- a/nixos/release-combined.nix
+++ b/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
@@ -43,24 +43,25 @@ in rec {
       name = "nixos-${nixos.channel.version}";
       meta = {
         description = "Release-critical builds for the NixOS channel";
-        maintainers = with pkgs.lib.maintainers; [ eelco fpletz ];
+        maintainers = with pkgs.lib.maintainers; [ eelco ];
       };
       constituents = pkgs.lib.concatLists [
         [ "nixos.channel" ]
         (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")
@@ -132,12 +134,14 @@ in rec {
         # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
         #(onFullSupported "nixos.tests.nfs3.simple")
         (onFullSupported "nixos.tests.nfs4.simple")
+        (onSystems ["x86_64-linux"] "nixos.tests.oci-containers.podman")
         (onFullSupported "nixos.tests.openssh")
         (onFullSupported "nixos.tests.pantheon")
         (onFullSupported "nixos.tests.php.fpm")
         (onFullSupported "nixos.tests.php.httpd")
         (onFullSupported "nixos.tests.php.pcre")
         (onFullSupported "nixos.tests.plasma5")
+        (onSystems ["x86_64-linux"] "nixos.tests.podman")
         (onFullSupported "nixos.tests.predictable-interface-names.predictableNetworkd")
         (onFullSupported "nixos.tests.predictable-interface-names.predictable")
         (onFullSupported "nixos.tests.predictable-interface-names.unpredictableNetworkd")
diff --git a/nixos/release-small.nix b/nixos/release-small.nix
index 1d51b4e7f28f..deb428d1bec0 100644
--- a/nixos/release-small.nix
+++ b/nixos/release-small.nix
@@ -4,7 +4,7 @@
 
 { 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 +31,7 @@ in rec {
     inherit (nixos') channel manual options iso_minimal amazonImage dummy;
     tests = {
       inherit (nixos'.tests)
+        acme
         containers-imperative
         containers-ip
         firewall
@@ -53,7 +54,8 @@ in rec {
       };
       boot = {
         inherit (nixos'.tests.boot)
-          biosCdrom;
+          biosCdrom
+          uefiCdrom;
       };
     };
   };
@@ -69,7 +71,7 @@ in rec {
       imagemagick
       jdk
       linux
-      mysql
+      mariadb
       nginx
       nodejs
       openssh
@@ -83,45 +85,57 @@ in rec {
       vim;
   };
 
-  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-conntrack"
+        "nixos.tests.nat.firewall"
+        "nixos.tests.nat.standalone"
+        # fails with kernel >= 5.15 https://github.com/NixOS/nixpkgs/pull/152505#issuecomment-1005049314
+        #"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"
+      ])
+    ];
   };
 
 }
diff --git a/nixos/release.nix b/nixos/release.nix
index 6b7564a9b972..919aa86a2d63 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -15,16 +15,29 @@ let
     (if stableBranch then "." else "pre") + "${toString nixpkgs.revCount}.${nixpkgs.shortRev}";
 
   # Run the tests for each platform.  You can run a test by doing
-  # e.g. ‘nix-build -A tests.login.x86_64-linux’, or equivalently,
-  # ‘nix-build tests/login.nix -A result’.
+  # e.g. ‘nix-build release.nix -A tests.login.x86_64-linux’,
+  # or equivalently, ‘nix-build tests/login.nix’.
+  # See also nixosTests in pkgs/top-level/all-packages.nix
   allTestsForSystem = system:
     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
+      # the nodes, without running VMs.
+      allDrivers =
+        import ./tests/all-tests.nix {
+        inherit system;
+        pkgs = import ./.. { inherit system; };
+        callTest = config: {
+          ${system} = hydraJob config.driver;
+        };
       };
     };
+
   allTests =
     foldAttrs recursiveUpdate {} (map allTestsForSystem supportedSystems);
 
@@ -138,6 +151,13 @@ in rec {
   # Build the initial ramdisk so Hydra can keep track of its size over time.
   initialRamdisk = buildFromConfig ({ ... }: { }) (config: config.system.build.initialRamdisk);
 
+  kexec = forMatchingSystems supportedSystems (system: (import lib/eval-config.nix {
+    inherit system;
+    modules = [
+      ./modules/installer/netboot/netboot-minimal.nix
+    ];
+  }).config.system.build.kexecTree);
+
   netboot = forMatchingSystems supportedSystems (system: makeNetboot {
     module = ./modules/installer/netboot/netboot-minimal.nix;
     inherit system;
@@ -149,14 +169,14 @@ in rec {
     inherit system;
   });
 
-  iso_plasma5 = forMatchingSystems [ "x86_64-linux" ] (system: makeIso {
-    module = ./modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix;
+  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 {
-    module = ./modules/installer/cd-dvd/installation-cd-graphical-gnome.nix;
+  iso_gnome = forMatchingSystems supportedSystems (system: makeIso {
+    module = ./modules/installer/cd-dvd/installation-cd-graphical-calamares-gnome.nix;
     type = "gnome";
     inherit system;
   });
@@ -201,6 +221,29 @@ in rec {
 
   );
 
+  # KVM image for proxmox in VMA format
+  proxmoxImage = forMatchingSystems [ "x86_64-linux" ] (system:
+    with import ./.. { inherit system; };
+
+    hydraJob ((import lib/eval-config.nix {
+      inherit system;
+      modules = [
+        ./modules/virtualisation/proxmox-image.nix
+      ];
+    }).config.system.build.VMA)
+  );
+
+  # LXC tarball for proxmox
+  proxmoxLXC = forMatchingSystems [ "x86_64-linux" ] (system:
+    with import ./.. { inherit system; };
+
+    hydraJob ((import lib/eval-config.nix {
+      inherit system;
+      modules = [
+        ./modules/virtualisation/proxmox-lxc.nix
+      ];
+    }).config.system.build.tarball)
+  );
 
   # A disk image that can be imported to Amazon EC2 and registered as an AMI
   amazonImage = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
@@ -298,39 +341,12 @@ in rec {
     "mkdir $out; ln -s $toplevel $out/dummy");
 
 
-  # Provide a tarball that can be unpacked into an SD card, and easily
-  # boot that system from uboot (like for the sheevaplug).
-  # The pc variant helps preparing the expression for the system tarball
-  # in a machine faster than the sheevpalug
-  /*
-  system_tarball_pc = forAllSystems (system: makeSystemTarball {
-    module = ./modules/installer/cd-dvd/system-tarball-pc.nix;
-    inherit system;
-  });
-  */
-
   # Provide container tarball for lxc, libvirt-lxc, docker-lxc, ...
   containerTarball = forAllSystems (system: makeSystemTarball {
     module = ./modules/virtualisation/lxc-container.nix;
     inherit system;
   });
 
-  /*
-  system_tarball_fuloong2f =
-    assert builtins.currentSystem == "mips64-linux";
-    makeSystemTarball {
-      module = ./modules/installer/cd-dvd/system-tarball-fuloong2f.nix;
-      system = "mips64-linux";
-    };
-
-  system_tarball_sheevaplug =
-    assert builtins.currentSystem == "armv5tel-linux";
-    makeSystemTarball {
-      module = ./modules/installer/cd-dvd/system-tarball-sheevaplug.nix;
-      system = "armv5tel-linux";
-    };
-  */
-
   tests = allTests;
 
   /* Build a bunch of typical closures so that Hydra can keep track of
diff --git a/nixos/tests/3proxy.nix b/nixos/tests/3proxy.nix
index dfc4b35a772d..647d9d57c7ff 100644
--- a/nixos/tests/3proxy.nix
+++ b/nixos/tests/3proxy.nix
@@ -1,6 +1,6 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
+{ lib, pkgs, ... }: {
   name = "3proxy";
-  meta = with pkgs.lib.maintainers; {
+  meta = with lib.maintainers; {
     maintainers = [ misuzu ];
   };
 
@@ -92,7 +92,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 = [
@@ -139,7 +139,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     peer0.wait_for_unit("network-online.target")
 
     peer1.wait_for_unit("3proxy.service")
-    peer1.wait_for_open_port("9999")
+    peer1.wait_for_open_port(9999)
 
     # test none auth
     peer0.succeed(
@@ -153,7 +153,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     )
 
     peer2.wait_for_unit("3proxy.service")
-    peer2.wait_for_open_port("9999")
+    peer2.wait_for_open_port(9999)
 
     # test iponly auth
     peer0.succeed(
@@ -167,7 +167,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     )
 
     peer3.wait_for_unit("3proxy.service")
-    peer3.wait_for_open_port("9999")
+    peer3.wait_for_open_port(9999)
 
     # test strong auth
     peer0.succeed(
@@ -186,4 +186,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/nixos/tests/acme.nix b/nixos/tests/acme.nix
index 2dd06a50f40b..d62bf0c0fd92 100644
--- a/nixos/tests/acme.nix
+++ b/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
@@ -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"):
@@ -578,7 +656,7 @@ in {
               webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
               wait_for_server()
               check_connection(client, test_domain)
-              rc, _ = client.execute(
+              rc, _s = client.execute(
                   f"openssl s_client -CAfile /tmp/ca.crt -connect {test_alias}:443"
                   " </dev/null 2>/dev/null | openssl x509 -noout -text"
                   f" | grep DNS: | grep {test_alias}"
@@ -594,4 +672,4 @@ in {
               wait_for_server()
               check_connection_key_bits(client, test_domain, "384")
     '';
-})
+}
diff --git a/nixos/tests/adguardhome.nix b/nixos/tests/adguardhome.nix
index ddbe8ff9c117..ec1cc1e497b5 100644
--- a/nixos/tests/adguardhome.nix
+++ b/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/nixos/tests/aesmd.nix b/nixos/tests/aesmd.nix
index 9f07426be8d8..5da661afd548 100644
--- a/nixos/tests/aesmd.nix
+++ b/nixos/tests/aesmd.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: {
+{ pkgs, lib, ... }: {
   name = "aesmd";
   meta = {
     maintainers = with lib.maintainers; [ veehaitch ];
@@ -59,4 +59,4 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
       assert aesmd_config == "whitelist url = http://nixos.org\nproxy type = direct\ndefault quoting type = ecdsa_256\n", "aesmd.conf differs"
   '';
-})
+}
diff --git a/nixos/tests/airsonic.nix b/nixos/tests/airsonic.nix
index 2f60c56f643b..69f979726bce 100644
--- a/nixos/tests/airsonic.nix
+++ b/nixos/tests/airsonic.nix
@@ -15,7 +15,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = ''
     def airsonic_is_up(_) -> bool:
-        return machine.succeed("curl --fail http://localhost:4040/login")
+        status, _ = machine.execute("curl --fail http://localhost:4040/login")
+        return status == 0
 
 
     machine.start()
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index ad426fcf4e0a..6f056de2ed5c 100644
--- a/nixos/tests/all-tests.nix
+++ b/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:
@@ -26,21 +42,46 @@ let
     featureFlags.minimalModules = {};
   };
   evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
-in
-{
-  _3proxy = handleTest ./3proxy.nix {};
-  acme = handleTest ./acme.nix {};
-  adguardhome = handleTest ./adguardhome.nix {};
-  aesmd = handleTest ./aesmd.nix {};
-  agate = handleTest ./web-servers/agate.nix {};
+
+  inherit
+    (rec {
+      doRunTest = arg: ((import ../lib/testing-python.nix { inherit system pkgs; }).evalTest {
+        imports = [ arg ];
+      }).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
+    ;
+
+in {
+  _3proxy = runTest ./3proxy.nix;
+  acme = runTest ./acme.nix;
+  adguardhome = runTest ./adguardhome.nix;
+  aesmd = runTest ./aesmd.nix;
+  agate = runTest ./web-servers/agate.nix;
   agda = handleTest ./agda.nix {};
   airsonic = handleTest ./airsonic.nix {};
   allTerminfo = handleTest ./all-terminfo.nix {};
+  alps = handleTest ./alps.nix {};
   amazon-init-shell = handleTest ./amazon-init-shell.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 {};
   avahi = handleTest ./avahi.nix {};
   avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
   babeld = handleTest ./babeld.nix {};
@@ -55,6 +96,7 @@ in
   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 {};
@@ -62,6 +104,8 @@ in
   breitbandmessung = handleTest ./breitbandmessung.nix {};
   brscan5 = handleTest ./brscan5.nix {};
   btrbk = handleTest ./btrbk.nix {};
+  btrbk-no-timer = handleTest ./btrbk-no-timer.nix {};
+  btrbk-section-order = handleTest ./btrbk-section-order.nix {};
   buildbot = handleTest ./buildbot.nix {};
   buildkite-agents = handleTest ./buildkite-agents.nix {};
   caddy = handleTest ./caddy.nix {};
@@ -69,21 +113,22 @@ 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; };
+  cassandra_4 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_4; };
   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 {};
   certmgr = handleTest ./certmgr.nix {};
-  cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {};
+  cfssl = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cfssl.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 {};
+  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 {};
+  cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {};
   cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
   collectd = handleTest ./collectd.nix {};
   consul = handleTest ./consul.nix {};
@@ -102,16 +147,16 @@ 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 {};
   custom-ca = handleTest ./custom-ca.nix {};
   croc = handleTest ./croc.nix {};
-  cryptpad = handleTest ./cryptpad.nix {};
   deluge = handleTest ./deluge.nix {};
-  dendrite = handleTest ./dendrite.nix {};
+  dendrite = handleTest ./matrix/dendrite.nix {};
   dex-oidc = handleTest ./dex-oidc.nix {};
   dhparams = handleTest ./dhparams.nix {};
   disable-installer-tools = handleTest ./disable-installer-tools.nix {};
@@ -120,16 +165,17 @@ 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-edge = handleTestOn ["x86_64-linux"] ./docker-edge.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 {};
   docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
   documize = handleTest ./documize.nix {};
+  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 {};
@@ -137,9 +183,12 @@ in
   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 {};
@@ -147,17 +196,20 @@ in
   ergo = handleTest ./ergo.nix {};
   ergochat = handleTest ./ergochat.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 {};
   fenics = handleTest ./fenics.nix {};
   ferm = handleTest ./ferm.nix {};
   firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
   firefox-esr    = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job
-  firefox-esr-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 {};
   fish = handleTest ./fish.nix {};
@@ -165,10 +217,14 @@ in
   fluentd = handleTest ./fluentd.nix {};
   fluidd = handleTest ./fluidd.nix {};
   fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
+  freenet = handleTest ./freenet.nix {};
   freeswitch = handleTest ./freeswitch.nix {};
+  freshrss = handleTest ./freshrss.nix {};
   frr = handleTest ./frr.nix {};
   fsck = handleTest ./fsck.nix {};
   ft2-clone = handleTest ./ft2-clone.nix {};
+  mimir = handleTest ./mimir.nix {};
+  garage = handleTest ./garage.nix {};
   gerrit = handleTest ./gerrit.nix {};
   geth = handleTest ./geth.nix {};
   ghostunnel = handleTest ./ghostunnel.nix {};
@@ -184,9 +240,11 @@ in
   gobgpd = handleTest ./gobgpd.nix {};
   gocd-agent = handleTest ./gocd-agent.nix {};
   gocd-server = handleTest ./gocd-server.nix {};
+  gollum = handleTest ./gollum.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 {};
   grocy = handleTest ./grocy.nix {};
@@ -199,14 +257,16 @@ in
   haste-server = handleTest ./haste-server.nix {};
   haproxy = handleTest ./haproxy.nix {};
   hardened = handleTest ./hardened.nix {};
-  hbase1 = handleTest ./hbase.nix { package=pkgs.hbase1; };
+  headscale = handleTest ./headscale.nix {};
+  healthchecks = handleTest ./web-apps/healthchecks.nix {};
   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
@@ -235,9 +295,9 @@ in
   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 {};
@@ -249,9 +309,10 @@ in
   jibri = handleTest ./jibri.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
   jitsi-meet = handleTest ./jitsi-meet.nix {};
-  k3s-single-node = handleTest ./k3s-single-node.nix {};
-  k3s-single-node-docker = handleTest ./k3s-single-node-docker.nix {};
+  k3s = handleTest ./k3s {};
   kafka = handleTest ./kafka.nix {};
+  kanidm = handleTest ./kanidm.nix {};
+  karma = handleTest ./karma.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 {};
@@ -260,22 +321,34 @@ in
   kerberos = handleTest ./kerberos/default.nix {};
   kernel-generic = handleTest ./kernel-generic.nix {};
   kernel-latest-ath-user-regd = handleTest ./kernel-latest-ath-user-regd.nix {};
+  keter = handleTest ./keter.nix {};
   kexec = handleTest ./kexec.nix {};
   keycloak = discoverTests (import ./keycloak.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 = handleTest ./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 {};
   locate = handleTest ./locate.nix {};
   login = handleTest ./login.nix {};
@@ -283,7 +356,6 @@ in
   loki = handleTest ./loki.nix {};
   lvm2 = handleTest ./lvm2 {};
   lxd = handleTest ./lxd.nix {};
-  lxd-image = handleTest ./lxd-image.nix {};
   lxd-nftables = handleTest ./lxd-nftables.nix {};
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
@@ -296,16 +368,17 @@ in
   mailhog = handleTest ./mailhog.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; });
   matomo = handleTest ./matomo.nix {};
-  matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {};
-  matrix-conduit = handleTest ./matrix-conduit.nix {};
-  matrix-synapse = handleTest ./matrix-synapse.nix {};
+  matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {};
+  matrix-conduit = handleTest ./matrix/conduit.nix {};
+  matrix-synapse = handleTest ./matrix/synapse.nix {};
   mattermost = handleTest ./mattermost.nix {};
   mediatomb = handleTest ./mediatomb.nix {};
   mediawiki = handleTest ./mediawiki.nix {};
   meilisearch = handleTest ./meilisearch.nix {};
   memcached = handleTest ./memcached.nix {};
+  merecat = handleTest ./merecat.nix {};
   metabase = handleTest ./metabase.nix {};
   minecraft = handleTest ./minecraft.nix {};
   minecraft-server = handleTest ./minecraft-server.nix {};
@@ -346,6 +419,7 @@ in
   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; };
@@ -363,7 +437,10 @@ 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 {};
@@ -378,9 +455,12 @@ in
   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 {};
   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 {};
@@ -406,33 +486,41 @@ in
   pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
   pam-u2f = handleTest ./pam/pam-u2f.nix {};
   pam-ussh = handleTest ./pam/pam-ussh.nix {};
+  pass-secret-service = handleTest ./pass-secret-service.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 {};
   pgadmin4 = handleTest ./pgadmin4.nix {};
   pgadmin4-standalone = handleTest ./pgadmin4-standalone.nix {};
   pgjwt = handleTest ./pgjwt.nix {};
   pgmanage = handleTest ./pgmanage.nix {};
+  phosh = handleTest ./phosh.nix {};
   php = handleTest ./php {};
-  php74 = handleTest ./php { php = pkgs.php74; };
   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-dnsname = handleTestOn ["aarch64-linux" "x86_64-linux"] ./podman/dnsname.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 {};
   postfix = handleTest ./postfix.nix {};
   postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
@@ -455,20 +543,21 @@ in
   proxy = handleTest ./proxy.nix {};
   prowlarr = handleTest ./prowlarr.nix {};
   pt2-clone = handleTest ./pt2-clone.nix {};
+  pykms = handleTest ./pykms.nix {};
+  public-inbox = handleTest ./public-inbox.nix {};
   pulseaudio = discoverTests (import ./pulseaudio.nix);
   qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.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 {};
   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 {};
-  riak = handleTest ./riak.nix {};
   robustirc-bridge = handleTest ./robustirc-bridge.nix {};
   roundcube = handleTest ./roundcube.nix {};
   rspamd = handleTest ./rspamd.nix {};
@@ -481,6 +570,7 @@ in
   samba = handleTest ./samba.nix {};
   samba-wsdd = handleTest ./samba-wsdd.nix {};
   sanoid = handleTest ./sanoid.nix {};
+  schleuder = handleTest ./schleuder.nix {};
   sddm = handleTest ./sddm.nix {};
   seafile = handleTest ./seafile.nix {};
   searx = handleTest ./searx.nix {};
@@ -504,13 +594,17 @@ in
   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 {};
   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-partition = handleTest ./swap-partition.nix {};
   sway = handleTest ./sway.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
@@ -521,12 +615,17 @@ 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-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-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 {};
@@ -537,11 +636,16 @@ 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-shutdown = handleTest ./systemd-shutdown.nix {};
   systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
   systemd-misc = handleTest ./systemd-misc.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 {};
@@ -553,12 +657,14 @@ in
   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 {};
@@ -573,11 +679,17 @@ in
   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; };
   vault = handleTest ./vault.nix {};
+  vault-dev = handleTest ./vault-dev.nix {};
   vault-postgresql = handleTest ./vault-postgresql.nix {};
   vaultwarden = handleTest ./vaultwarden.nix {};
   vector = handleTest ./vector.nix {};
@@ -587,6 +699,7 @@ 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 {};
   wiki-js = handleTest ./wiki-js.nix {};
   wine = handleTest ./wine.nix {};
@@ -595,11 +708,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 {};
@@ -607,6 +723,7 @@ in
   yabar = handleTest ./yabar.nix {};
   yggdrasil = handleTest ./yggdrasil.nix {};
   zammad = handleTest ./zammad.nix {};
+  zeronet-conservancy = handleTest ./zeronet-conservancy.nix {};
   zfs = handleTest ./zfs.nix {};
   zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
   zoneminder = handleTest ./zoneminder.nix {};
diff --git a/nixos/tests/alps.nix b/nixos/tests/alps.nix
new file mode 100644
index 000000000000..9756f2d4da15
--- /dev/null
+++ b/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/nixos/tests/atuin.nix b/nixos/tests/atuin.nix
new file mode 100644
index 000000000000..85213d1e53ea
--- /dev/null
+++ b/nixos/tests/atuin.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  testPort = 8888;
+  testUser = "testerman";
+  testPass = "password";
+  testEmail = "test.testerman@test.com";
+in
+with lib;
+{
+  name = "atuin";
+  meta.maintainers = with pkgs.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/nixos/tests/auth-mysql.nix b/nixos/tests/auth-mysql.nix
new file mode 100644
index 000000000000..0ed4b050a69a
--- /dev/null
+++ b/nixos/tests/auth-mysql.nix
@@ -0,0 +1,177 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  dbUser = "nixos_auth";
+  dbPassword = "topsecret123";
+  dbName = "auth";
+
+  mysqlUsername = "mysqltest";
+  mysqlPassword = "topsecretmysqluserpassword123";
+  mysqlGroup = "mysqlusers";
+
+  localUsername = "localtest";
+  localPassword = "topsecretlocaluserpassword123";
+
+  mysqlInit = pkgs.writeText "mysqlInit" ''
+      CREATE USER '${dbUser}'@'localhost' IDENTIFIED BY '${dbPassword}';
+      CREATE DATABASE ${dbName};
+      GRANT ALL PRIVILEGES ON ${dbName}.* TO '${dbUser}'@'localhost';
+      FLUSH PRIVILEGES;
+
+      USE ${dbName};
+      CREATE TABLE `groups` (
+        rowid int(11) NOT NULL auto_increment,
+        gid int(11) NOT NULL,
+        name char(255) NOT NULL,
+        PRIMARY KEY (rowid)
+      );
+
+      CREATE TABLE `users` (
+        name varchar(255) NOT NULL,
+        uid int(11) NOT NULL auto_increment,
+        gid int(11) NOT NULL,
+        password varchar(255) NOT NULL,
+        PRIMARY KEY (uid),
+        UNIQUE (name)
+      ) AUTO_INCREMENT=5000;
+
+      INSERT INTO `users` (name, uid, gid, password) VALUES
+      ('${mysqlUsername}', 5000, 5000, SHA2('${mysqlPassword}', 256));
+      INSERT INTO `groups` (name, gid) VALUES ('${mysqlGroup}', 5000);
+    '';
+in
+{
+  name = "auth-mysql";
+  meta.maintainers = with lib.maintainers; [ netali ];
+
+  nodes.machine =
+    { ... }:
+    {
+      services.mysql = {
+        enable = true;
+        package = pkgs.mariadb;
+        settings.mysqld.bind-address = "127.0.0.1";
+        initialScript = mysqlInit;
+      };
+
+      users.users.${localUsername} = {
+        isNormalUser = true;
+        password = localPassword;
+      };
+
+      security.pam.services.login.makeHomeDir = true;
+
+      users.mysql = {
+        enable = true;
+        host = "127.0.0.1";
+        user = dbUser;
+        database = dbName;
+        passwordFile = "${builtins.toFile "dbPassword" dbPassword}";
+        pam = {
+          table = "users";
+          userColumn = "name";
+          passwordColumn = "password";
+          passwordCrypt = "sha256";
+          disconnectEveryOperation = true;
+        };
+        nss = {
+          getpwnam = ''
+            SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
+            FROM users \
+            WHERE name='%1$s' \
+            LIMIT 1
+          '';
+          getpwuid = ''
+            SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
+            FROM users \
+            WHERE id=%1$u \
+            LIMIT 1
+          '';
+          getspnam = ''
+            SELECT name, password, 1, 0, 99999, 7, 0, -1, 0 \
+            FROM users \
+            WHERE name='%1$s' \
+            LIMIT 1
+          '';
+          getpwent = ''
+            SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
+            FROM users
+          '';
+          getspent = ''
+            SELECT name, password, 1, 0, 99999, 7, 0, -1, 0 \
+            FROM users
+          '';
+          getgrnam = ''
+            SELECT name, 'x', gid FROM groups WHERE name='%1$s' LIMIT 1
+          '';
+          getgrgid = ''
+            SELECT name, 'x', gid FROM groups WHERE gid='%1$u' LIMIT 1
+          '';
+          getgrent = ''
+            SELECT name, 'x', gid FROM groups
+          '';
+          memsbygid = ''
+            SELECT name FROM users WHERE gid=%1$u
+          '';
+          gidsbymem = ''
+            SELECT gid FROM users WHERE name='%1$s'
+          '';
+        };
+      };
+    };
+
+  testScript = ''
+    def switch_to_tty(tty_number):
+        machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
+        machine.send_key(f"alt-f{tty_number}")
+        machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
+        machine.wait_for_unit(f"getty@tty{tty_number}.service")
+        machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
+
+
+    def try_login(tty_number, username, password):
+        machine.wait_until_tty_matches(tty_number, "login: ")
+        machine.send_chars(f"{username}\n")
+        machine.wait_until_tty_matches(tty_number, f"login: {username}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches(tty_number, "Password: ")
+        machine.send_chars(f"{password}\n")
+
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("mysql.service")
+    machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+    with subtest("Local login"):
+        switch_to_tty("2")
+        try_login("2", "${localUsername}", "${localPassword}")
+
+        machine.wait_until_succeeds("pgrep -u ${localUsername} bash")
+        machine.send_chars("id > local_id.txt\n")
+        machine.wait_for_file("/home/${localUsername}/local_id.txt")
+        machine.succeed("cat /home/${localUsername}/local_id.txt | grep 'uid=1000(${localUsername}) gid=100(users) groups=100(users)'")
+
+    with subtest("Local incorrect login"):
+        switch_to_tty("3")
+        try_login("3", "${localUsername}", "wrongpassword")
+
+        machine.wait_until_tty_matches("3", "Login incorrect")
+        machine.wait_until_tty_matches("3", "login:")
+
+    with subtest("MySQL login"):
+        switch_to_tty("4")
+        try_login("4", "${mysqlUsername}", "${mysqlPassword}")
+
+        machine.wait_until_succeeds("pgrep -u ${mysqlUsername} bash")
+        machine.send_chars("id > mysql_id.txt\n")
+        machine.wait_for_file("/home/${mysqlUsername}/mysql_id.txt")
+        machine.succeed("cat /home/${mysqlUsername}/mysql_id.txt | grep 'uid=5000(${mysqlUsername}) gid=5000(${mysqlGroup}) groups=5000(${mysqlGroup})'")
+
+    with subtest("MySQL incorrect login"):
+        switch_to_tty("5")
+        try_login("5", "${mysqlUsername}", "wrongpassword")
+
+        machine.wait_until_tty_matches("5", "Login incorrect")
+        machine.wait_until_tty_matches("5", "login:")
+    '';
+})
diff --git a/nixos/tests/bazarr.nix b/nixos/tests/bazarr.nix
index c3337611aa29..e59833e5e945 100644
--- a/nixos/tests/bazarr.nix
+++ b/nixos/tests/bazarr.nix
@@ -20,7 +20,7 @@ in
 
   testScript = ''
     machine.wait_for_unit("bazarr.service")
-    machine.wait_for_open_port("${toString port}")
+    machine.wait_for_open_port(${toString port})
     machine.succeed("curl --fail http://localhost:${toString port}/")
   '';
 })
diff --git a/nixos/tests/bitcoind.nix b/nixos/tests/bitcoind.nix
index 04655b7f6a51..7726a23d853e 100644
--- a/nixos/tests/bitcoind.nix
+++ b/nixos/tests/bitcoind.nix
@@ -13,9 +13,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
       };
     };
+
+    environment.etc."test.blank".text = "";
     services.bitcoind."testnet" = {
       enable = true;
-      configFile = "/test.blank";
+      configFile = "/etc/test.blank";
       testnet = true;
       rpc = {
         port = 18332;
diff --git a/nixos/tests/bootspec.nix b/nixos/tests/bootspec.nix
new file mode 100644
index 000000000000..077dff918e0d
--- /dev/null
+++ b/nixos/tests/bootspec.nix
@@ -0,0 +1,170 @@
+{ 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/bootspec/boot.json")
+
+      bootspec = json.loads(machine.succeed("jq -r '.v1' /run/current-system/bootspec/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 '.v1.specialisation.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 == sp_in_fs['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 = {
+        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 '.v1.extensions.osRelease' /run/current-system/boot.json)")
+
+      assert current_os_release == bootspec_os_release, "Filename referenced by extension has unexpected contents"
+    '';
+  };
+
+}
diff --git a/nixos/tests/borgbackup.nix b/nixos/tests/borgbackup.nix
index d3cd6c66bfeb..9afe4d537da4 100644
--- a/nixos/tests/borgbackup.nix
+++ b/nixos/tests/borgbackup.nix
@@ -99,6 +99,18 @@ 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";
+        };
+
       };
     };
 
@@ -204,5 +216,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/nixos/tests/btrbk-no-timer.nix b/nixos/tests/btrbk-no-timer.nix
new file mode 100644
index 000000000000..4fcab8839c89
--- /dev/null
+++ b/nixos/tests/btrbk-no-timer.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+  {
+    name = "btrbk-no-timer";
+    meta.maintainers = with lib.maintainers; [ oxalica ];
+
+    nodes.machine = { ... }: {
+      environment.systemPackages = with pkgs; [ btrfs-progs ];
+      services.btrbk.instances.local = {
+        onCalendar = null;
+        settings.volume."/mnt" = {
+          snapshot_dir = "btrbk/local";
+          subvolume = "to_backup";
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # Create btrfs partition at /mnt
+      machine.succeed("truncate --size=128M /data_fs")
+      machine.succeed("mkfs.btrfs /data_fs")
+      machine.succeed("mkdir /mnt")
+      machine.succeed("mount /data_fs /mnt")
+      machine.succeed("btrfs subvolume create /mnt/to_backup")
+      machine.succeed("mkdir -p /mnt/btrbk/local")
+
+      # The service should not have any triggering timer.
+      unit = machine.get_unit_info('btrbk-local.service')
+      assert "TriggeredBy" not in unit
+
+      # Manually starting the service should still work.
+      machine.succeed("echo foo > /mnt/to_backup/bar")
+      machine.start_job("btrbk-local.service")
+      machine.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+    '';
+  })
diff --git a/nixos/tests/btrbk-section-order.nix b/nixos/tests/btrbk-section-order.nix
new file mode 100644
index 000000000000..20f1afcf80ec
--- /dev/null
+++ b/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/nixos/tests/caddy.nix b/nixos/tests/caddy.nix
index 16436ab52800..c4abd33d0395 100644
--- a/nixos/tests/caddy.nix
+++ b/nixos/tests/caddy.nix
@@ -61,7 +61,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     ''
       url = "http://localhost/example.html"
       webserver.wait_for_unit("caddy")
-      webserver.wait_for_open_port("80")
+      webserver.wait_for_open_port(80)
 
 
       def check_etag(url):
@@ -95,13 +95,13 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           webserver.succeed(
               "${justReloadSystem}/bin/switch-to-configuration test >&2"
           )
-          webserver.wait_for_open_port("8080")
+          webserver.wait_for_open_port(8080)
 
       with subtest("multiple configs are correctly merged"):
           webserver.succeed(
               "${multipleConfigs}/bin/switch-to-configuration test >&2"
           )
-          webserver.wait_for_open_port("8080")
-          webserver.wait_for_open_port("8081")
+          webserver.wait_for_open_port(8080)
+          webserver.wait_for_open_port(8081)
     '';
 })
diff --git a/nixos/tests/cagebreak.nix b/nixos/tests/cagebreak.nix
index 1dcc910f9744..1fef7cb57cfc 100644
--- a/nixos/tests/cagebreak.nix
+++ b/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/nixos/tests/chromium.nix b/nixos/tests/chromium.nix
index 3815dca76220..cdfdcc9bcdd2 100644
--- a/nixos/tests/chromium.nix
+++ b/nixos/tests/chromium.nix
@@ -39,18 +39,22 @@ 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;
   };
 
   enableOCR = true;
 
-  machine.imports = [ ./common/user-account.nix ./common/x11.nix ];
-  machine.virtualisation.memorySize = 2047;
-  machine.test-support.displayManager.auto.user = user;
-  machine.environment = {
-    systemPackages = [ chromiumPkg ];
-    variables."XAUTHORITY" = "/home/alice/.Xauthority";
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ./common/x11.nix ];
+    virtualisation.memorySize = 2047;
+    test-support.displayManager.auto.user = user;
+    environment = {
+      systemPackages = [ chromiumPkg ];
+      variables."XAUTHORITY" = "/home/alice/.Xauthority";
+    };
   };
 
   testScript = let
@@ -63,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)
@@ -82,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")
@@ -160,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
@@ -240,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/nixos/tests/cinnamon.nix b/nixos/tests/cinnamon.nix
new file mode 100644
index 000000000000..f0add4142929
--- /dev/null
+++ b/nixos/tests/cinnamon.nix
@@ -0,0 +1,68 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "cinnamon";
+
+  meta = with lib; {
+    maintainers = teams.cinnamon.members;
+  };
+
+  nodes.machine = { nodes, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.desktopManager.cinnamon.enable = true;
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.config.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/nixos/tests/cloud-init-hostname.nix b/nixos/tests/cloud-init-hostname.nix
new file mode 100644
index 000000000000..7c657cc9f6f9
--- /dev/null
+++ b/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/nixos/tests/cloud-init.nix b/nixos/tests/cloud-init.nix
index 9feb8d5c1526..786e01add7d4 100644
--- a/nixos/tests/cloud-init.nix
+++ b/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/nixos/tests/cockroachdb.nix b/nixos/tests/cockroachdb.nix
index d793842f0ab2..5b1e1a7dee1f 100644
--- a/nixos/tests/cockroachdb.nix
+++ b/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/nixos/tests/common/acme/client/default.nix b/nixos/tests/common/acme/client/default.nix
index 9dbe345e7a01..503e610d1ac9 100644
--- a/nixos/tests/common/acme/client/default.nix
+++ b/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/nixos/tests/common/acme/server/acme.test.cert.pem b/nixos/tests/common/acme/server/acme.test.cert.pem
index 76b0d916a817..48f488ab8f90 100644
--- a/nixos/tests/common/acme/server/acme.test.cert.pem
+++ b/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/nixos/tests/common/acme/server/acme.test.key.pem b/nixos/tests/common/acme/server/acme.test.key.pem
index 741df99a372e..4837f19b3024 100644
--- a/nixos/tests/common/acme/server/acme.test.key.pem
+++ b/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/nixos/tests/common/acme/server/ca.cert.pem b/nixos/tests/common/acme/server/ca.cert.pem
index 5c33e879b675..b6f2b9e3a91f 100644
--- a/nixos/tests/common/acme/server/ca.cert.pem
+++ b/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/nixos/tests/common/acme/server/ca.key.pem b/nixos/tests/common/acme/server/ca.key.pem
index ed46f5dccf46..5d46c025788f 100644
--- a/nixos/tests/common/acme/server/ca.key.pem
+++ b/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/nixos/tests/common/acme/server/default.nix b/nixos/tests/common/acme/server/default.nix
index 450d49e60399..b81f860125c8 100644
--- a/nixos/tests/common/acme/server/default.nix
+++ b/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
 #     ];
 #   };
 #
@@ -81,8 +81,8 @@ in {
       type = 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.
       '';
     };
@@ -90,10 +90,10 @@ in {
       type = 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/nixos/tests/common/acme/server/generate-certs.nix b/nixos/tests/common/acme/server/generate-certs.nix
index cd8fe0dffca1..85c751c56ad5 100644
--- a/nixos/tests/common/acme/server/generate-certs.nix
+++ b/nixos/tests/common/acme/server/generate-certs.nix
@@ -10,7 +10,11 @@ let
   domain = conf.domain;
 in mkDerivation {
   name = "test-certs";
-  buildInputs = [ minica ];
+  buildInputs = [ (minica.overrideAttrs (old: {
+    prePatch = ''
+      sed -i 's_NotAfter: time.Now().AddDate(2, 0, 30),_NotAfter: time.Now().AddDate(20, 0, 0),_' main.go
+    '';
+  })) ];
   phases = [ "buildPhase" "installPhase" ];
 
   buildPhase = ''
diff --git a/nixos/tests/common/auto.nix b/nixos/tests/common/auto.nix
index da6b14e9f160..f2ab82f88ff7 100644
--- a/nixos/tests/common/auto.nix
+++ b/nixos/tests/common/auto.nix
@@ -19,17 +19,17 @@ in
 
       enable = 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 {
         default = "root";
-        description = "The user account to login automatically.";
+        description = lib.mdDoc "The user account to login automatically.";
       };
 
     };
diff --git a/nixos/tests/common/ec2.nix b/nixos/tests/common/ec2.nix
index 64b0a91ac1f7..6ed420e0aae7 100644
--- a/nixos/tests/common/ec2.nix
+++ b/nixos/tests/common/ec2.nix
@@ -46,7 +46,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.
diff --git a/nixos/tests/common/lxd/config.yaml b/nixos/tests/common/lxd/config.yaml
new file mode 100644
index 000000000000..3bb667ed43f7
--- /dev/null
+++ b/nixos/tests/common/lxd/config.yaml
@@ -0,0 +1,24 @@
+storage_pools:
+  - name: default
+    driver: dir
+    config:
+      source: /var/lxd-pool
+
+networks:
+  - name: lxdbr0
+    type: bridge
+    config:
+      ipv4.address: auto
+      ipv6.address: none
+
+profiles:
+  - name: default
+    devices:
+      eth0:
+        name: eth0
+        network: lxdbr0
+        type: nic
+      root:
+        path: /
+        pool: default
+        type: disk
diff --git a/nixos/tests/common/resolver.nix b/nixos/tests/common/resolver.nix
index 09a74de20faa..3ddf730668c4 100644
--- a/nixos/tests/common/resolver.nix
+++ b/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/nixos/tests/containers-custom-pkgs.nix b/nixos/tests/containers-custom-pkgs.nix
index 9894e6643762..e8740ac63134 100644
--- a/nixos/tests/containers-custom-pkgs.nix
+++ b/nixos/tests/containers-custom-pkgs.nix
@@ -9,7 +9,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let
 in {
   name = "containers-custom-pkgs";
   meta = {
-    maintainers = with lib.maintainers; [ adisbladis earvstedt ];
+    maintainers = with lib.maintainers; [ adisbladis erikarvstedt ];
   };
 
   nodes.machine = { config, ... }: {
diff --git a/nixos/tests/containers-ephemeral.nix b/nixos/tests/containers-ephemeral.nix
index c9fe2778cacd..cb4b7d4eba0f 100644
--- a/nixos/tests/containers-ephemeral.nix
+++ b/nixos/tests/containers-ephemeral.nix
@@ -33,10 +33,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine.succeed("nixos-container start webserver")
 
     with subtest("Container got its own root folder"):
-        machine.succeed("ls /run/containers/webserver")
+        machine.succeed("ls /run/nixos-containers/webserver")
 
     with subtest("Container persistent directory is not created"):
-        machine.fail("ls /var/lib/containers/webserver")
+        machine.fail("ls /var/lib/nixos-containers/webserver")
 
     # Since "start" returns after the container has reached
     # multi-user.target, we should now be able to access it.
@@ -49,6 +49,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         machine.fail(f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null")
 
     with subtest("Container's root folder was removed"):
-        machine.fail("ls /run/containers/webserver")
+        machine.fail("ls /run/nixos-containers/webserver")
   '';
 })
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 44d2e50288b4..3007efaf8871 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -18,8 +18,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       # container available within the VM, because we don't have network access.
       virtualisation.additionalPaths = let
         emptyContainer = import ../lib/eval-config.nix {
-          inherit (config.nixpkgs.localSystem) system;
           modules = lib.singleton {
+            nixpkgs = { inherit (config.nixpkgs) localSystem; };
+
             containers.foo.config = {
               system.stateVersion = "18.03";
             };
@@ -69,8 +70,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
       with subtest(f"Put the root of {id2} into a bind mount"):
           machine.succeed(
-              f"mv /var/lib/containers/{id2} /id2-bindmount",
-              f"mount --bind /id2-bindmount /var/lib/containers/{id1}",
+              f"mv /var/lib/nixos-containers/{id2} /id2-bindmount",
+              f"mount --bind /id2-bindmount /var/lib/nixos-containers/{id1}",
           )
 
           ip1 = machine.succeed(f"nixos-container show-ip {id1}").rstrip()
@@ -88,7 +89,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           "Create a directory with a dummy file and bind-mount it into both containers."
       ):
           for id in id1, id2:
-              important_path = f"/var/lib/containers/{id}/very/important/data"
+              important_path = f"/var/lib/nixos-containers/{id}/very/important/data"
               machine.succeed(
                   f"mkdir -p {important_path}",
                   f"mount --bind /nested-bindmount {important_path}",
@@ -154,13 +155,13 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           machine.succeed("grep -qF 'important data' /nested-bindmount/dummy")
 
       with subtest("Ensure that the container path is gone"):
-          print(machine.succeed("ls -lsa /var/lib/containers"))
-          machine.succeed(f"test ! -e /var/lib/containers/{id1}")
+          print(machine.succeed("ls -lsa /var/lib/nixos-containers"))
+          machine.succeed(f"test ! -e /var/lib/nixos-containers/{id1}")
 
       with subtest("Ensure that a failed container creation doesn'leave any state"):
           machine.fail(
               "nixos-container create b0rk --config-file ${brokenCfg}"
           )
-          machine.succeed("test ! -e /var/lib/containers/b0rk")
+          machine.succeed("test ! -e /var/lib/nixos-containers/b0rk")
     '';
 })
diff --git a/nixos/tests/containers-tmpfs.nix b/nixos/tests/containers-tmpfs.nix
index 7a2c835b120a..cf5b81656afe 100644
--- a/nixos/tests/containers-tmpfs.nix
+++ b/nixos/tests/containers-tmpfs.nix
@@ -62,7 +62,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           machine.succeed(
               tmpfs_cmd("touch /root/test.file"),
               tmpfs_cmd("ls -l  /root | grep -q test.file"),
-              "test -e /var/lib/containers/tmpfs/root/test.file",
+              "test -e /var/lib/nixos-containers/tmpfs/root/test.file",
           )
 
       with subtest(
@@ -73,7 +73,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
               tmpfs_cmd("touch /some/random/path/test.file"),
               tmpfs_cmd("test -e /some/random/path/test.file"),
           )
-          machine.fail("test -e /var/lib/containers/tmpfs/some/random/path/test.file")
+          machine.fail("test -e /var/lib/nixos-containers/tmpfs/some/random/path/test.file")
 
       with subtest(
           "files created in the hosts container dir in a path where a tmpfs "
@@ -81,9 +81,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           + "the do not exist in the tmpfs"
       ):
           machine.succeed(
-              "touch /var/lib/containers/tmpfs/var/test.file",
-              "test -e /var/lib/containers/tmpfs/var/test.file",
-              "ls -l /var/lib/containers/tmpfs/var/ | grep -q test.file 2>/dev/null",
+              "touch /var/lib/nixos-containers/tmpfs/var/test.file",
+              "test -e /var/lib/nixos-containers/tmpfs/var/test.file",
+              "ls -l /var/lib/nixos-containers/tmpfs/var/ | grep -q test.file 2>/dev/null",
           )
           machine.fail(tmpfs_cmd("ls -l /var | grep -q test.file"))
     '';
diff --git a/nixos/tests/containers-unified-hierarchy.nix b/nixos/tests/containers-unified-hierarchy.nix
new file mode 100644
index 000000000000..978d59e12c8a
--- /dev/null
+++ b/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/nixos/tests/convos.nix b/nixos/tests/convos.nix
index a13870d17084..a5dafed8f6f0 100644
--- a/nixos/tests/convos.nix
+++ b/nixos/tests/convos.nix
@@ -23,8 +23,8 @@ in
 
   testScript = ''
     machine.wait_for_unit("convos")
-    machine.wait_for_open_port("${toString port}")
-    machine.succeed("journalctl -u convos | grep -q 'Listening at.*${toString port}'")
+    machine.wait_for_open_port(${toString port})
+    machine.succeed("journalctl -u convos | grep -q 'application available at.*${toString port}'")
     machine.succeed("curl -f http://localhost:${toString port}/")
   '';
 })
diff --git a/nixos/tests/corerad.nix b/nixos/tests/corerad.nix
index 638010f92f44..b6f5d7fc6f75 100644
--- a/nixos/tests/corerad.nix
+++ b/nixos/tests/corerad.nix
@@ -1,5 +1,6 @@
 import ./make-test-python.nix (
   {
+    name = "corerad";
     nodes = {
       router = {config, pkgs, ...}: {
         config = {
diff --git a/nixos/tests/couchdb.nix b/nixos/tests/couchdb.nix
index 453f5dcd66e8..b57072d6be2d 100644
--- a/nixos/tests/couchdb.nix
+++ b/nixos/tests/couchdb.nix
@@ -20,7 +20,7 @@ with lib;
 {
   name = "couchdb";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ fpletz ];
+    maintainers = [ ];
   };
 
   nodes = {
diff --git a/nixos/tests/cri-o.nix b/nixos/tests/cri-o.nix
index d3a8713d6a9b..08e1e8f36b06 100644
--- a/nixos/tests/cri-o.nix
+++ b/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/nixos/tests/cryptpad.nix b/nixos/tests/cryptpad.nix
deleted file mode 100644
index 895f291abaca..000000000000
--- a/nixos/tests/cryptpad.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-import ./make-test-python.nix ({ lib, ... }:
-
-with lib;
-
-{
-  name = "cryptpad";
-  meta.maintainers = with maintainers; [ davhau ];
-
-  nodes.machine =
-    { pkgs, ... }:
-    { services.cryptpad.enable = true; };
-
-  testScript = ''
-    machine.wait_for_unit("cryptpad.service")
-    machine.wait_for_open_port("3000")
-    machine.succeed("curl -L --fail http://localhost:3000/sheet")
-  '';
-})
diff --git a/nixos/tests/custom-ca.nix b/nixos/tests/custom-ca.nix
index 60c6c82223a9..25a7b6fdea46 100644
--- a/nixos/tests/custom-ca.nix
+++ b/nixos/tests/custom-ca.nix
@@ -1,11 +1,18 @@
 # Checks that `security.pki` options are working in curl and the main browser
-# engines: Gecko (via Firefox), Chromium, QtWebEngine (Falkon) and WebKitGTK
-# (via Midori). The test checks that certificates issued by a custom trusted
-# CA are accepted but those from an unknown CA are rejected.
+# engines: Gecko (via Firefox), Chromium, QtWebEngine (via qutebrowser) and
+# WebKitGTK (via Midori). The test checks that certificates issued by a custom
+# trusted CA are accepted but those from an unknown CA are rejected.
 
-import ./make-test-python.nix ({ pkgs, lib, ... }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
 
 let
+  inherit (pkgs) lib;
+
   makeCert = { caName, domain }: pkgs.runCommand "example-cert"
   { buildInputs = [ pkgs.gnutls ]; }
   ''
@@ -68,24 +75,8 @@ let
       domain = "bad.example.com";
     };
 
-in
-
-{
-  name = "custom-ca";
-  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
-
-  enableOCR = true;
-
-  nodes.machine = { pkgs, ... }:
-    { imports = [ ./common/user-account.nix ./common/x11.nix ];
-
-      # chromium-based browsers refuse to run as root
-      test-support.displayManager.auto.user = "alice";
-
-      # browsers may hang with the default memory
-      virtualisation.memorySize = 600;
-
-      networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
+  webserverConfig =
+    { networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
       security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
 
       services.nginx.enable = true;
@@ -107,73 +98,98 @@ in
             return 200 'It does not work!';
           '';
         };
-
-      environment.systemPackages = with pkgs; [
-        xdotool
-        firefox
-        chromium
-        qutebrowser
-        midori
-      ];
     };
 
-  testScript = ''
-    from typing import Tuple
-    def execute_as(user: str, cmd: str) -> Tuple[int, str]:
-        """
-        Run a shell command as a specific user.
-        """
-        return machine.execute(f"sudo -u {user} {cmd}")
-
-
-    def wait_for_window_as(user: str, cls: str) -> None:
-        """
-        Wait until a X11 window of a given user appears.
-        """
-
-        def window_is_visible(last_try: bool) -> bool:
-            ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}")
-            if last_try:
-                machine.log(f"Last chance to match {cls} on the window list")
-            return ret == 0
-
-        with machine.nested("Waiting for a window to appear"):
-            retry(window_is_visible)
-
-
-    machine.start()
-
-    with subtest("Good certificate is trusted in curl"):
-        machine.wait_for_unit("nginx")
-        machine.wait_for_open_port(443)
-        machine.succeed("curl -fv https://good.example.com")
-
-    with subtest("Unknown CA is untrusted in curl"):
-        machine.fail("curl -fv https://bad.example.com")
-
-    browsers = {
-      "firefox": "Security Risk",
-      "chromium": "not private",
-      "qutebrowser -T": "Certificate error",
-      "midori": "Security"
-    }
-
-    machine.wait_for_x()
-    for command, error in browsers.items():
-        browser = command.split()[0]
-        with subtest("Good certificate is trusted in " + browser):
-            execute_as(
-                "alice", f"{command} https://good.example.com >&2 &"
-            )
-            wait_for_window_as("alice", browser)
-            machine.wait_for_text("It works!")
-            machine.screenshot("good" + browser)
-            execute_as("alice", "xdotool key ctrl+w")  # close tab
-
-        with subtest("Unknown CA is untrusted in " + browser):
-            execute_as("alice", f"{command} https://bad.example.com >&2 &")
-            machine.wait_for_text(error)
-            machine.screenshot("bad" + browser)
-            machine.succeed("pkill -f " + browser)
-  '';
-})
+  curlTest = makeTest {
+    name = "custom-ca-curl";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+    nodes.machine = { ... }: webserverConfig;
+    testScript = ''
+        with subtest("Good certificate is trusted in curl"):
+            machine.wait_for_unit("nginx")
+            machine.wait_for_open_port(443)
+            machine.succeed("curl -fv https://good.example.com")
+
+        with subtest("Unknown CA is untrusted in curl"):
+            machine.fail("curl -fv https://bad.example.com")
+    '';
+  };
+
+  mkBrowserTest = browser: testParams: makeTest {
+    name = "custom-ca-${browser}";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    enableOCR = true;
+
+    nodes.machine = { pkgs, ... }:
+      { imports =
+          [ ./common/user-account.nix
+            ./common/x11.nix
+            webserverConfig
+          ];
+
+        # chromium-based browsers refuse to run as root
+        test-support.displayManager.auto.user = "alice";
+
+        # browsers may hang with the default memory
+        virtualisation.memorySize = 600;
+
+        environment.systemPackages = [ pkgs.xdotool pkgs.${browser} ];
+      };
+
+    testScript = ''
+      from typing import Tuple
+      def execute_as(user: str, cmd: str) -> Tuple[int, str]:
+          """
+          Run a shell command as a specific user.
+          """
+          return machine.execute(f"sudo -u {user} {cmd}")
+
+
+      def wait_for_window_as(user: str, cls: str) -> None:
+          """
+          Wait until a X11 window of a given user appears.
+          """
+
+          def window_is_visible(last_try: bool) -> bool:
+              ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}")
+              if last_try:
+                  machine.log(f"Last chance to match {cls} on the window list")
+              return ret == 0
+
+          with machine.nested("Waiting for a window to appear"):
+              retry(window_is_visible)
+
+
+      machine.start()
+      machine.wait_for_x()
+
+      command = "${browser} ${testParams.args or ""}"
+      with subtest("Good certificate is trusted in ${browser}"):
+          execute_as(
+              "alice", f"{command} https://good.example.com >&2 &"
+          )
+          wait_for_window_as("alice", "${browser}")
+          machine.sleep(4)
+          execute_as("alice", "xdotool key ctrl+r")  # reload to be safe
+          machine.wait_for_text("It works!")
+          machine.screenshot("good${browser}")
+          execute_as("alice", "xdotool key ctrl+w")  # close tab
+
+      with subtest("Unknown CA is untrusted in ${browser}"):
+          execute_as("alice", f"{command} https://bad.example.com >&2 &")
+          machine.wait_for_text("${testParams.error}")
+          machine.screenshot("bad${browser}")
+    '';
+  };
+
+in
+
+{
+  curl = curlTest;
+} // pkgs.lib.mapAttrs mkBrowserTest {
+  firefox = { error = "Security Risk"; };
+  chromium = { error = "not private"; };
+  qutebrowser = { args = "-T"; error = "Certificate error"; };
+  midori = { args = "-p"; error = "Security"; };
+}
diff --git a/nixos/tests/deluge.nix b/nixos/tests/deluge.nix
index 33c57ce7c36c..e8945fdea003 100644
--- a/nixos/tests/deluge.nix
+++ b/nixos/tests/deluge.nix
@@ -47,15 +47,17 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     simple.wait_for_unit("deluged")
     simple.wait_for_unit("delugeweb")
-    simple.wait_for_open_port("8112")
+    simple.wait_for_open_port(8112)
     declarative.wait_for_unit("network.target")
     declarative.wait_until_succeeds("curl --fail http://simple:8112")
 
     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/nixos/tests/dhparams.nix b/nixos/tests/dhparams.nix
index a0de2911777c..021042fafdb1 100644
--- a/nixos/tests/dhparams.nix
+++ b/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/nixos/tests/discourse.nix b/nixos/tests/discourse.nix
index cfac5f84a62f..35ca083c6c4e 100644
--- a/nixos/tests/discourse.nix
+++ b/nixos/tests/discourse.nix
@@ -30,6 +30,7 @@ import ./make-test-python.nix (
         virtualisation.memorySize = 2048;
         virtualisation.cores = 4;
         virtualisation.useNixStoreImage = true;
+        virtualisation.writableStore = false;
 
         imports = [ common/user-account.nix ];
 
diff --git a/nixos/tests/dnscrypt-proxy2.nix b/nixos/tests/dnscrypt-proxy2.nix
index 1ba5d983e9b9..a75a745d3553 100644
--- a/nixos/tests/dnscrypt-proxy2.nix
+++ b/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/nixos/tests/docker-edge.nix b/nixos/tests/docker-edge.nix
deleted file mode 100644
index c6a1a0830189..000000000000
--- a/nixos/tests/docker-edge.nix
+++ /dev/null
@@ -1,49 +0,0 @@
-# This test runs docker and checks if simple container starts
-
-import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "docker";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ nequissimus offline ];
-  };
-
-  nodes = {
-    docker =
-      { pkgs, ... }:
-        {
-          virtualisation.docker.enable = true;
-          virtualisation.docker.package = pkgs.docker-edge;
-
-          users.users = {
-            noprivs = {
-              isNormalUser = true;
-              description = "Can't access the docker daemon";
-              password = "foobar";
-            };
-
-            hasprivs = {
-              isNormalUser = true;
-              description = "Can access the docker daemon";
-              password = "foobar";
-              extraGroups = [ "docker" ];
-            };
-          };
-        };
-    };
-
-  testScript = ''
-    start_all()
-
-    docker.wait_for_unit("sockets.target")
-    docker.succeed("tar cv --files-from /dev/null | docker import - scratchimg")
-    docker.succeed(
-        "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
-    )
-    docker.succeed("docker ps | grep sleeping")
-    docker.succeed("sudo -u hasprivs docker ps")
-    docker.fail("sudo -u noprivs docker ps")
-    docker.succeed("docker stop sleeping")
-
-    # Must match version 4 times to ensure client and server git commits and versions are correct
-    docker.succeed('[ $(docker version | grep ${pkgs.docker-edge.version} | wc -l) = "4" ]')
-  '';
-})
diff --git a/nixos/tests/docker-registry.nix b/nixos/tests/docker-registry.nix
index 1d449db45191..316b7c9b9727 100644
--- a/nixos/tests/docker-registry.nix
+++ b/nixos/tests/docker-registry.nix
@@ -35,7 +35,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     registry.start()
     registry.wait_for_unit("docker-registry.service")
-    registry.wait_for_open_port("8080")
+    registry.wait_for_open_port(8080)
     client1.succeed("docker push registry:8080/scratch")
 
     client2.start()
diff --git a/nixos/tests/docker-tools-cross.nix b/nixos/tests/docker-tools-cross.nix
index 8791ec258127..14cb14ceeaea 100644
--- a/nixos/tests/docker-tools-cross.nix
+++ b/nixos/tests/docker-tools-cross.nix
@@ -24,7 +24,11 @@ let
   hello1 = remoteCrossPkgs.dockerTools.buildImage {
     name = "hello1";
     tag = "latest";
-    contents = remoteCrossPkgs.hello;
+    copyToRoot = remoteCrossPkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ remoteCrossPkgs.hello ];
+    };
   };
 
   hello2 = remoteCrossPkgs.dockerTools.buildLayeredImage {
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 80859ac7a96e..98704ecb2fb6 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -346,7 +346,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker load --input='${examples.layeredImageWithFakeRootCommands}'"
         )
         docker.succeed(
-            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/jane | grep -E ^1000$'"
+            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/alice | grep -E ^1000$'"
         )
 
     with subtest("Ensure docker load on merged images loads all of the constituent images"):
@@ -389,7 +389,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker load --input='${examples.mergedBashFakeRoot}'"
         )
         docker.succeed(
-            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/jane | grep -E ^1000$'"
+            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/alice | grep -E ^1000$'"
         )
 
     with subtest("The image contains store paths referenced by the fakeRootCommands output"):
@@ -419,5 +419,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/nixos/tests/docker.nix b/nixos/tests/docker.nix
index dee7480eb4a9..93baa198088b 100644
--- a/nixos/tests/docker.nix
+++ b/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/nixos/tests/documize.nix b/nixos/tests/documize.nix
index 528bf5338ce0..fda79b1a0931 100644
--- a/nixos/tests/documize.nix
+++ b/nixos/tests/documize.nix
@@ -47,9 +47,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
             " --data 'dbhash={}'"
             " --data 'title=NixOS'"
             " --data 'message=Docs'"
-            " --data 'firstname=John'"
-            " --data 'lastname=Doe'"
-            " --data 'email=john.doe@nixos.org'"
+            " --data 'firstname=Bob'"
+            " --data 'lastname=Foobar'"
+            " --data 'email=bob.foobar@nixos.org'"
             " --data 'password=verysafe'"
             " -f localhost:3000/api/setup"
         ).format(dbhash)
diff --git a/nixos/tests/dolibarr.nix b/nixos/tests/dolibarr.nix
new file mode 100644
index 000000000000..2f012a0c67da
--- /dev/null
+++ b/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/nixos/tests/domination.nix b/nixos/tests/domination.nix
index 09027740ab8d..409a7f3029c4 100644
--- a/nixos/tests/domination.nix
+++ b/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/nixos/tests/ec2.nix b/nixos/tests/ec2.nix
index aa3c2b7051f6..e649761d029d 100644
--- a/nixos/tests/ec2.nix
+++ b/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/nixos/tests/ecryptfs.nix b/nixos/tests/ecryptfs.nix
index e3cfb2ed998c..1c67d307a00e 100644
--- a/nixos/tests/ecryptfs.nix
+++ b/nixos/tests/ecryptfs.nix
@@ -11,16 +11,16 @@ import ./make-test-python.nix ({ ... }:
 
   testScript = ''
     def login_as_alice():
-        machine.wait_until_tty_matches(1, "login: ")
+        machine.wait_until_tty_matches("1", "login: ")
         machine.send_chars("alice\n")
-        machine.wait_until_tty_matches(1, "Password: ")
+        machine.wait_until_tty_matches("1", "Password: ")
         machine.send_chars("foobar\n")
-        machine.wait_until_tty_matches(1, "alice\@machine")
+        machine.wait_until_tty_matches("1", "alice\@machine")
 
 
     def logout():
         machine.send_chars("logout\n")
-        machine.wait_until_tty_matches(1, "login: ")
+        machine.wait_until_tty_matches("1", "login: ")
 
 
     machine.wait_for_unit("default.target")
@@ -36,7 +36,7 @@ import ./make-test-python.nix ({ ... }:
     with subtest("Log alice in (ecryptfs passwhrase is wrapped during first login)"):
         login_as_alice()
         machine.send_chars("logout\n")
-        machine.wait_until_tty_matches(1, "login: ")
+        machine.wait_until_tty_matches("1", "login: ")
 
     # Why do I need to do this??
     machine.succeed("su alice -c ecryptfs-umount-private || true")
diff --git a/nixos/tests/endlessh-go.nix b/nixos/tests/endlessh-go.nix
new file mode 100644
index 000000000000..b261dbf1c560
--- /dev/null
+++ b/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/nixos/tests/endlessh.nix b/nixos/tests/endlessh.nix
new file mode 100644
index 000000000000..be742a749fdd
--- /dev/null
+++ b/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/nixos/tests/evcc.nix b/nixos/tests/evcc.nix
new file mode 100644
index 000000000000..c223977a9d82
--- /dev/null
+++ b/nixos/tests/evcc.nix
@@ -0,0 +1,97 @@
+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
+        assert "ERROR" not in output
+
+    with subtest("Check systemd hardening"):
+        _, output = machine.execute("systemd-analyze security evcc.service | grep -v '✓'")
+        machine.log(output)
+  '';
+})
diff --git a/nixos/tests/extra-python-packages.nix b/nixos/tests/extra-python-packages.nix
new file mode 100644
index 000000000000..7a48077cf98b
--- /dev/null
+++ b/nixos/tests/extra-python-packages.nix
@@ -0,0 +1,13 @@
+import ./make-test-python.nix ({ ... }:
+  {
+    name = "extra-python-packages";
+
+    extraPythonPackages = p: [ p.numpy ];
+
+    nodes = { };
+
+    testScript = ''
+      import numpy as np
+      assert str(np.zeros(4) == "array([0., 0., 0., 0.])")
+    '';
+  })
diff --git a/nixos/tests/fcitx/default.nix b/nixos/tests/fcitx/default.nix
index 78b322d351d3..c132249fcb24 100644
--- a/nixos/tests/fcitx/default.nix
+++ b/nixos/tests/fcitx/default.nix
@@ -5,6 +5,7 @@ import ../make-test-python.nix (
     # 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,
diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
index c773368a3e60..3f9cea6662fb 100644
--- a/nixos/tests/firefox.nix
+++ b/nixos/tests/firefox.nix
@@ -1,5 +1,14 @@
-import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
-  name = "firefox";
+import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }:
+let firefoxPackage' = firefoxPackage.override (args: {
+      extraPrefsFiles = (args.extraPrefsFiles or []) ++ [
+        # make sure that autoplay is enabled by default for the audio test
+        (builtins.toString (builtins.toFile "autoplay-pref.js" ''defaultPref("media.autoplay.default",0);''))
+      ];
+  });
+
+in
+{
+  name = firefoxPackage'.unwrapped.pname;
   meta = with pkgs.lib.maintainers; {
     maintainers = [ eelco shlevy ];
   };
@@ -9,7 +18,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
 
     { imports = [ ./common/x11.nix ];
       environment.systemPackages = [
-        firefoxPackage
+        firefoxPackage'
         pkgs.xdotool
       ];
 
@@ -54,7 +63,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
 
 
       @contextmanager
-      def audio_recording(machine: Machine) -> None:
+      def record_audio(machine: Machine):
           """
           Perform actions while recording the
           machine audio output.
@@ -64,7 +73,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
           machine.systemctl("stop audio-recorder")
 
 
-      def wait_for_sound(machine: Machine) -> None:
+      def wait_for_sound(machine: Machine):
           """
           Wait until any sound has been emitted.
           """
@@ -88,15 +97,15 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
 
       with subtest("Wait until Firefox has finished loading the Valgrind docs page"):
           machine.execute(
-              "xterm -e 'firefox file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' >&2 &"
+              "xterm -e '${firefoxPackage'.unwrapped.binaryName} file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' >&2 &"
           )
           machine.wait_for_window("Valgrind")
           machine.sleep(40)
 
       with subtest("Check whether Firefox can play sound"):
-          with audio_recording(machine):
+          with record_audio(machine):
               machine.succeed(
-                  "firefox file://${pkgs.sound-theme-freedesktop}/share/sounds/freedesktop/stereo/phone-incoming-call.oga >&2 &"
+                  "${firefoxPackage'.unwrapped.binaryName} file://${pkgs.sound-theme-freedesktop}/share/sounds/freedesktop/stereo/phone-incoming-call.oga >&2 &"
               )
               wait_for_sound(machine)
           machine.copy_from_vm("/tmp/record.wav")
diff --git a/nixos/tests/freenet.nix b/nixos/tests/freenet.nix
new file mode 100644
index 000000000000..96dbb4caa129
--- /dev/null
+++ b/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/nixos/tests/freeswitch.nix b/nixos/tests/freeswitch.nix
index bcc6a9cb3586..bfb7339ec3c0 100644
--- a/nixos/tests/freeswitch.nix
+++ b/nixos/tests/freeswitch.nix
@@ -24,6 +24,6 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   testScript = ''
     node0.wait_for_unit("freeswitch.service")
     # Wait for SIP port to be open
-    node0.wait_for_open_port("5060")
+    node0.wait_for_open_port(5060)
   '';
 })
diff --git a/nixos/tests/freshrss.nix b/nixos/tests/freshrss.nix
new file mode 100644
index 000000000000..7bdbf29e9230
--- /dev/null
+++ b/nixos/tests/freshrss.nix
@@ -0,0 +1,19 @@
+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";
+    };
+  };
+
+  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/nixos/tests/fscrypt.nix b/nixos/tests/fscrypt.nix
new file mode 100644
index 000000000000..03367979359b
--- /dev/null
+++ b/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/nixos/tests/garage.nix b/nixos/tests/garage.nix
new file mode 100644
index 000000000000..dc1f83e7f8f3
--- /dev/null
+++ b/nixos/tests/garage.nix
@@ -0,0 +1,169 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+let
+    mkNode = { replicationMode, publicV6Address ? "::1" }: { pkgs, ... }: {
+      networking.interfaces.eth1.ipv6.addresses = [{
+        address = publicV6Address;
+        prefixLength = 64;
+      }];
+
+      networking.firewall.allowedTCPPorts = [ 3901 3902 ];
+
+      services.garage = {
+        enable = true;
+        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 {
+  name = "garage";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  };
+
+  nodes = {
+    single_node = mkNode { replicationMode = "none"; };
+    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 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)
+
+    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))
+  '';
+})
diff --git a/nixos/tests/ghostunnel.nix b/nixos/tests/ghostunnel.nix
index 8bea64854021..91a7b7085f67 100644
--- a/nixos/tests/ghostunnel.nix
+++ b/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/nixos/tests/gitea.nix b/nixos/tests/gitea.nix
index 037fc7b31bfa..68a2566c1191 100644
--- a/nixos/tests/gitea.nix
+++ b/nixos/tests/gitea.nix
@@ -18,7 +18,7 @@ let
         services.gitea = {
           enable = true;
           database = { inherit type; };
-          disableRegistration = true;
+          settings.service.DISABLE_REGISTRATION = true;
         };
         environment.systemPackages = [ pkgs.gitea pkgs.jq ];
         services.openssh.enable = true;
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index e1916ed36f31..d9d75d1cbd89 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -1,9 +1,31 @@
-# This test runs gitlab and checks if it works
+# This test runs gitlab and performs the following tests:
+# - Creating users
+# - Pushing commits
+#   - over the API
+#   - over SSH
+# - 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, ... }:
+
+with lib;
 
 let
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
   initialRootPassword = "notproduction";
-in
-import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
+  rootProjectId = "2";
+
+  aliceUsername = "alice";
+  aliceUserId = "2";
+  alicePassword = "alicepassword";
+  aliceProjectId = "2";
+  aliceProjectName = "test-alice";
+
+  bobUsername = "bob";
+  bobUserId = "3";
+  bobPassword = "bobpassword";
+  bobProjectId = "3";
+in {
   name = "gitlab";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ globin yayayayaka ];
@@ -16,6 +38,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
       virtualisation.memorySize = if pkgs.stdenv.is64bit then 4096 else 2047;
       virtualisation.cores = 4;
       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";
@@ -31,6 +55,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
         };
       };
 
+      services.openssh.enable = true;
+
       services.dovecot2 = {
         enable = true;
         enableImap = true;
@@ -77,8 +103,43 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
         password = initialRootPassword;
       });
 
-      createProject = pkgs.writeText "create-project.json" (builtins.toJSON {
-        name = "test";
+      createUserAlice = pkgs.writeText "create-user-alice.json" (builtins.toJSON rec {
+        username = aliceUsername;
+        name = username;
+        email = "alice@localhost";
+        password = alicePassword;
+        skip_confirmation = true;
+      });
+
+      createUserBob = pkgs.writeText "create-user-bob.json" (builtins.toJSON rec {
+        username = bobUsername;
+        name = username;
+        email = "bob@localhost";
+        password = bobPassword;
+        skip_confirmation = true;
+      });
+
+      aliceAuth = pkgs.writeText "alice-auth.json" (builtins.toJSON {
+        grant_type = "password";
+        username = aliceUsername;
+        password = alicePassword;
+      });
+
+      bobAuth = pkgs.writeText "bob-auth.json" (builtins.toJSON {
+        grant_type = "password";
+        username = bobUsername;
+        password = bobPassword;
+      });
+
+      aliceAddSSHKey = pkgs.writeText "alice-add-ssh-key.json" (builtins.toJSON {
+        id = aliceUserId;
+        title = "snakeoil@nixos";
+        key = snakeOilPublicKey;
+      });
+
+      createProjectAlice = pkgs.writeText "create-project-alice.json" (builtins.toJSON {
+        name = aliceProjectName;
+        visibility = "public";
       });
 
       putFile = pkgs.writeText "put-file.json" (builtins.toJSON {
@@ -89,6 +150,23 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
         commit_message = "create a new file";
       });
 
+      mergeRequest = pkgs.writeText "merge-request.json" (builtins.toJSON {
+        id = bobProjectId;
+        target_project_id = aliceProjectId;
+        source_branch = "master";
+        target_branch = "master";
+        title = "Add some other file";
+      });
+
+      newIssue = pkgs.writeText "new-issue.json" (builtins.toJSON {
+        title = "useful issue title";
+      });
+
+      closeIssue = pkgs.writeText "close-issue.json" (builtins.toJSON {
+        issue_iid = 1;
+        state_event = "close";
+      });
+
       # Wait for all GitLab services to be fully started.
       waitForServices = ''
         gitlab.wait_for_unit("gitaly.service")
@@ -105,6 +183,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
       # The actual test of GitLab. Only push data to GitLab if
       # `doSetup` is is true.
       test = doSetup: ''
+        GIT_SSH_COMMAND = "ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null"
+
         gitlab.succeed(
             "curl -isSf http://gitlab | grep -i location | grep http://gitlab/users/sign_in"
         )
@@ -115,27 +195,222 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
             "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 ''
-        gitlab.succeed(
-            """[ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createProject} http://gitlab/api/v4/projects)" = "201" ]"""
-        )
-        gitlab.succeed(
-            """[ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${putFile} http://gitlab/api/v4/projects/2/repository/files/some-file.txt)" = "201" ]"""
-        )
+        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" ]"""
+            )
+            gitlab.succeed(
+                "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${aliceAuth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers-alice"
+            )
+
+        with subtest("Create user Bob"):
+            gitlab.succeed(
+                """ [ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createUserBob} http://gitlab/api/v4/users)" = "201" ]"""
+            )
+            gitlab.succeed(
+                "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${bobAuth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers-bob"
+            )
+
+        with subtest("Setup Git and SSH for Alice"):
+            gitlab.succeed("git config --global user.name Alice")
+            gitlab.succeed("git config --global user.email alice@nixos.invalid")
+            gitlab.succeed("mkdir -m 700 /root/.ssh")
+            gitlab.succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa")
+            gitlab.succeed("chmod 600 /root/.ssh/id_ecdsa")
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice -d @${aliceAddSSHKey} \
+                    http://gitlab/api/v4/user/keys)" = "201" ]
+                """
+            )
+
+        with subtest("Create a new repository"):
+            # Alice creates a new repository
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice \
+                    -d @${createProjectAlice} \
+                    http://gitlab/api/v4/projects)" = "201" ]
+                """
+            )
+
+            # Alice commits an initial commit
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice \
+                    -d @${putFile} \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/files/some-file.txt)" = "201" ]"""
+            )
+
+        with subtest("git clone over HTTP"):
+            gitlab.succeed(
+                """git clone http://gitlab/alice/${aliceProjectName}.git clone-via-http""",
+                timeout=15
+            )
+
+        with subtest("Push a commit via SSH"):
+            gitlab.succeed(
+                f"""GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git clone gitlab@gitlab:alice/${aliceProjectName}.git""",
+                timeout=15
+            )
+            gitlab.succeed(
+                """echo "a commit sent over ssh" > ${aliceProjectName}/ssh.txt"""
+            )
+            gitlab.succeed(
+                """
+                cd ${aliceProjectName} || exit 1
+                git add .
+                """
+            )
+            gitlab.succeed(
+                """
+                cd ${aliceProjectName} || exit 1
+                git commit -m "Add a commit to be sent over ssh"
+                """
+            )
+            gitlab.succeed(
+                f"""
+                cd ${aliceProjectName} || exit 1
+                GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git push --set-upstream origin master
+                """,
+                timeout=15
+            )
+
+        with subtest("Fork a project"):
+            # Bob forks Alice's project
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/fork)" = "201" ]
+                """
+            )
+
+            # Bob creates a commit
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    -d @${putFile} \
+                    http://gitlab/api/v4/projects/${bobProjectId}/repository/files/some-other-file.txt)" = "201" ]
+                """
+            )
+
+        with subtest("Create a Merge Request"):
+            # Bob opens a merge request against Alice's repository
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    -d @${mergeRequest} \
+                    http://gitlab/api/v4/projects/${bobProjectId}/merge_requests)" = "201" ]
+                """
+            )
+
+            # Alice merges the MR
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X PUT \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice \
+                    -d @${mergeRequest} \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/merge_requests/1/merge)" = "200" ]
+                """
+            )
+
+        with subtest("Create an Issue"):
+            # Bob opens an issue on Alice's repository
+            gitlab.succeed(
+                """[ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    -d @${newIssue} \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/issues)" = "201" ]
+                """
+            )
+
+            # Alice closes the issue
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X PUT \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice -d @${closeIssue} http://gitlab/api/v4/projects/${aliceProjectId}/issues/1)" = "200" ]
+                """
+            )
       '' + ''
-        gitlab.succeed(
-            """[ "$(curl -o /dev/null -w '%{http_code}' -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.gz)" = "200" ]"""
-        )
-        gitlab.succeed(
-            """curl -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.gz > /tmp/archive.tar.gz"""
-        )
-        gitlab.succeed(
-            """[ "$(curl -o /dev/null -w '%{http_code}' -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.bz2)" = "200" ]"""
-        )
-        gitlab.succeed(
-            """curl -o /dev/null -w '%{http_code}' -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.bz2 > /tmp/archive.tar.bz2"""
-        )
-        gitlab.succeed("test -s /tmp/archive.tar.gz")
-        gitlab.succeed("test -s /tmp/archive.tar.bz2")
+        with subtest("Download archive.tar.gz"):
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz)" = "200" ]
+                """
+            )
+            gitlab.succeed(
+                """
+                curl \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz > /tmp/archive.tar.gz
+                """
+            )
+            gitlab.succeed("test -s /tmp/archive.tar.gz")
+
+        with subtest("Download archive.tar.bz2"):
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2)" = "200" ]
+                """
+            )
+            gitlab.succeed(
+                """
+                curl \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2 > /tmp/archive.tar.bz2
+                """
+            )
+            gitlab.succeed("test -s /tmp/archive.tar.bz2")
       '';
 
   in ''
diff --git a/nixos/tests/gitolite.nix b/nixos/tests/gitolite.nix
index 128677cebde3..9b3af59e4fbd 100644
--- a/nixos/tests/gitolite.nix
+++ b/nixos/tests/gitolite.nix
@@ -107,7 +107,7 @@ in
     with subtest("gitolite server starts"):
         server.wait_for_unit("gitolite-init.service")
         server.wait_for_unit("sshd.service")
-        client.succeed("ssh gitolite@server info")
+        client.succeed("ssh -n gitolite@server info")
 
     with subtest("admin can clone and configure gitolite-admin.git"):
         client.succeed(
diff --git a/nixos/tests/gocd-agent.nix b/nixos/tests/gocd-agent.nix
index 686d0b971d30..9301a88ec05d 100644
--- a/nixos/tests/gocd-agent.nix
+++ b/nixos/tests/gocd-agent.nix
@@ -36,7 +36,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   testScript = ''
     start_all()
     agent.wait_for_unit("gocd-server")
-    agent.wait_for_open_port("8153")
+    agent.wait_for_open_port(8153)
     agent.wait_for_unit("gocd-agent")
     agent.wait_until_succeeds(
         "curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].uuid"
diff --git a/nixos/tests/gollum.nix b/nixos/tests/gollum.nix
new file mode 100644
index 000000000000..833db87f2f32
--- /dev/null
+++ b/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.config.services.gollum.port})
+  '';
+})
diff --git a/nixos/tests/grafana-agent.nix b/nixos/tests/grafana-agent.nix
new file mode 100644
index 000000000000..a9f34d8cea31
--- /dev/null
+++ b/nixos/tests/grafana-agent.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+  let
+    nodes = {
+      machine = {
+        services.grafana-agent = {
+          enable = true;
+        };
+      };
+    };
+  in
+  {
+    name = "grafana-agent";
+
+    meta = with lib.maintainers; {
+      maintainers = [ zimbatm ];
+    };
+
+    inherit nodes;
+
+    testScript = ''
+      start_all()
+
+      with subtest("Grafana-agent is running"):
+          machine.wait_for_unit("grafana-agent.service")
+          machine.wait_for_open_port(12345)
+          machine.succeed(
+              "curl -sSfN http://127.0.0.1:12345/-/healthy"
+          )
+          machine.shutdown()
+    '';
+  })
diff --git a/nixos/tests/grafana.nix b/nixos/tests/grafana/basic.nix
index 174d664d8772..8bf4caad7fbf 100644
--- a/nixos/tests/grafana.nix
+++ b/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/nixos/tests/grafana/default.nix b/nixos/tests/grafana/default.nix
new file mode 100644
index 000000000000..9c2622571800
--- /dev/null
+++ b/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/nixos/tests/grafana/provision/contact-points.yaml b/nixos/tests/grafana/provision/contact-points.yaml
new file mode 100644
index 000000000000..2a5f14e75e2d
--- /dev/null
+++ b/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/nixos/tests/grafana/provision/dashboards.yaml b/nixos/tests/grafana/provision/dashboards.yaml
new file mode 100644
index 000000000000..dc83fe6b892d
--- /dev/null
+++ b/nixos/tests/grafana/provision/dashboards.yaml
@@ -0,0 +1,6 @@
+apiVersion: 1
+
+providers:
+  - name: 'default'
+    options:
+      path: /var/lib/grafana/dashboards
diff --git a/nixos/tests/grafana/provision/datasources.yaml b/nixos/tests/grafana/provision/datasources.yaml
new file mode 100644
index 000000000000..ccf9481db7f3
--- /dev/null
+++ b/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/nixos/tests/grafana/provision/default.nix b/nixos/tests/grafana/provision/default.nix
new file mode 100644
index 000000000000..1eb927632eb7
--- /dev/null
+++ b/nixos/tests/grafana/provision/default.nix
@@ -0,0 +1,251 @@
+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"}}";
+        };
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "L /var/lib/grafana/dashboards/test.json 0700 grafana grafana - ${pkgs.writeText "test.json" (builtins.readFile ./test_dashboard.json)}"
+    ];
+  };
+
+  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/nixos/tests/grafana/provision/mute-timings.yaml b/nixos/tests/grafana/provision/mute-timings.yaml
new file mode 100644
index 000000000000..1f47f7c18f0c
--- /dev/null
+++ b/nixos/tests/grafana/provision/mute-timings.yaml
@@ -0,0 +1,4 @@
+apiVersion: 1
+
+muteTimes:
+  - name: "Test Mute Timing"
diff --git a/nixos/tests/grafana/provision/policies.yaml b/nixos/tests/grafana/provision/policies.yaml
new file mode 100644
index 000000000000..eb31126c4ba5
--- /dev/null
+++ b/nixos/tests/grafana/provision/policies.yaml
@@ -0,0 +1,4 @@
+apiVersion: 1
+
+policies:
+  - receiver: "Test Contact Point"
diff --git a/nixos/tests/grafana/provision/rules.yaml b/nixos/tests/grafana/provision/rules.yaml
new file mode 100644
index 000000000000..946539c8cb69
--- /dev/null
+++ b/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/nixos/tests/grafana/provision/templates.yaml b/nixos/tests/grafana/provision/templates.yaml
new file mode 100644
index 000000000000..09df247b3451
--- /dev/null
+++ b/nixos/tests/grafana/provision/templates.yaml
@@ -0,0 +1,5 @@
+apiVersion: 1
+
+templates:
+  - name: "Test Template"
+    template: "Test message"
diff --git a/nixos/tests/grafana/provision/test_dashboard.json b/nixos/tests/grafana/provision/test_dashboard.json
new file mode 100644
index 000000000000..6e7a5b37f22b
--- /dev/null
+++ b/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/nixos/tests/graphite.nix b/nixos/tests/graphite.nix
index 496f16846ea6..de6cd8a50e17 100644
--- a/nixos/tests/graphite.nix
+++ b/nixos/tests/graphite.nix
@@ -12,14 +12,8 @@ import ./make-test-python.nix ({ pkgs, ... } :
               SECRET_KEY = "abcd";
             '';
           };
-          api = {
-            enable = true;
-            port = 8082;
-            finders = [ ];
-          };
           carbon.enableCache = true;
-          seyren.enable = false;  # Implicitely requires openssl-1.0.2u which is marked insecure
-          beacon.enable = true;
+          seyren.enable = false;  # Implicitly requires openssl-1.0.2u which is marked insecure
         };
       };
   };
@@ -28,21 +22,15 @@ import ./make-test-python.nix ({ pkgs, ... } :
     start_all()
     one.wait_for_unit("default.target")
     one.wait_for_unit("graphiteWeb.service")
-    one.wait_for_unit("graphiteApi.service")
-    one.wait_for_unit("graphite-beacon.service")
     one.wait_for_unit("carbonCache.service")
     # The services above are of type "simple". systemd considers them active immediately
     # even if they're still in preStart (which takes quite long for graphiteWeb).
     # Wait for ports to open so we're sure the services are up and listening.
     one.wait_for_open_port(8080)
-    one.wait_for_open_port(8082)
     one.wait_for_open_port(2003)
     one.succeed('echo "foo 1 `date +%s`" | nc -N localhost 2003')
     one.wait_until_succeeds(
         "curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"
     )
-    one.wait_until_succeeds(
-        "curl 'http://localhost:8082/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"
-    )
   '';
 })
diff --git a/nixos/tests/grocy.nix b/nixos/tests/grocy.nix
index fe0ddd341486..48bbc9f7d3fa 100644
--- a/nixos/tests/grocy.nix
+++ b/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/nixos/tests/hadoop/default.nix b/nixos/tests/hadoop/default.nix
index d2a97cbeffb8..479690adc064 100644
--- a/nixos/tests/hadoop/default.nix
+++ b/nixos/tests/hadoop/default.nix
@@ -4,4 +4,5 @@
   all = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hadoop.nix { inherit package; };
   hdfs = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hdfs.nix { inherit package; };
   yarn = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./yarn.nix { inherit package; };
+  hbase = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hbase.nix { inherit package; };
 }
diff --git a/nixos/tests/hadoop/hbase.nix b/nixos/tests/hadoop/hbase.nix
new file mode 100644
index 000000000000..d9d2dac0f658
--- /dev/null
+++ b/nixos/tests/hadoop/hbase.nix
@@ -0,0 +1,84 @@
+# Test a minimal hbase cluster
+{ pkgs, ... }:
+import ../make-test-python.nix ({ hadoop ? pkgs.hadoop, hbase ? pkgs.hbase, ... }:
+with pkgs.lib;
+{
+  name = "hadoop-hbase";
+
+  nodes = let
+    coreSite = {
+      "fs.defaultFS" = "hdfs://namenode:8020";
+    };
+    defOpts = {
+      enable = true;
+      openFirewall = true;
+    };
+    zookeeperQuorum = "zookeeper";
+  in {
+    zookeeper = { ... }: {
+      services.zookeeper.enable = true;
+      networking.firewall.allowedTCPPorts = [ 2181 ];
+    };
+    namenode = { ... }: {
+      services.hadoop = {
+        hdfs = {
+          namenode = defOpts // { formatOnInit = true; };
+        };
+        inherit coreSite;
+      };
+    };
+    datanode = { ... }: {
+      virtualisation.diskSize = 8192;
+      services.hadoop = {
+        hdfs.datanode = defOpts;
+        inherit coreSite;
+      };
+    };
+
+    master = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          master = defOpts // { initHDFS = true; };
+        };
+      };
+    };
+    regionserver = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          regionServer = defOpts;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    # wait for HDFS cluster
+    namenode.wait_for_unit("hdfs-namenode")
+    namenode.wait_for_unit("network.target")
+    namenode.wait_for_open_port(8020)
+    namenode.wait_for_open_port(9870)
+    datanode.wait_for_unit("hdfs-datanode")
+    datanode.wait_for_unit("network.target")
+    datanode.wait_for_open_port(9864)
+    datanode.wait_for_open_port(9866)
+    datanode.wait_for_open_port(9867)
+
+    # wait for ZK
+    zookeeper.wait_for_unit("zookeeper")
+    zookeeper.wait_for_open_port(2181)
+
+    # wait for HBase to start up
+    master.wait_for_unit("hbase-master")
+    regionserver.wait_for_unit("hbase-regionserver")
+
+    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")
+  '';
+})
diff --git a/nixos/tests/hadoop/yarn.nix b/nixos/tests/hadoop/yarn.nix
index 1bf8e3831f67..08c8ff857d8c 100644
--- a/nixos/tests/hadoop/yarn.nix
+++ b/nixos/tests/hadoop/yarn.nix
@@ -19,7 +19,7 @@ import ../make-test-python.nix ({ package, ... }: {
           enable = true;
           openFirewall = true;
         };
-        yarnSite = options.services.hadoop.yarnSite.default // {
+        yarnSite = {
           "yarn.resourcemanager.hostname" = "resourcemanager";
           "yarn.nodemanager.log-dirs" = "/tmp/userlogs";
         };
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
index 3afa8ebf2b5f..ccb858168547 100644
--- a/nixos/tests/hardened.nix
+++ b/nixos/tests/hardened.nix
@@ -12,6 +12,11 @@ import ./make-test-python.nix ({ pkgs, ... } : {
       imports = [ ../modules/profiles/hardened.nix ];
       environment.memoryAllocator.provider = "graphene-hardened";
       nix.settings.sandbox = false;
+      nixpkgs.overlays = [
+        (final: super: {
+          dhcpcd = super.dhcpcd.override { enablePrivSep = false; };
+        })
+      ];
       virtualisation.emptyDiskImages = [ 4096 ];
       boot.initrd.postDeviceCommands = ''
         ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
@@ -85,8 +90,8 @@ import ./make-test-python.nix ({ pkgs, ... } : {
 
       # Test Nix dæmon usage
       with subtest("nix-daemon cannot be used by all users"):
-          machine.fail("su -l nobody -s /bin/sh -c 'nix ping-store'")
-          machine.succeed("su -l alice -c 'nix ping-store'")
+          machine.fail("su -l nobody -s /bin/sh -c 'nix --extra-experimental-features nix-command ping-store'")
+          machine.succeed("su -l alice -c 'nix --extra-experimental-features nix-command ping-store'")
 
 
       # Test kernel image protection
diff --git a/nixos/tests/hbase.nix b/nixos/tests/hbase.nix
index a449d24dd6fd..7d8e32f81603 100644
--- a/nixos/tests/hbase.nix
+++ b/nixos/tests/hbase.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, package ? pkgs.hbase, ... }:
 {
-  name = "hbase";
+  name = "hbase-standalone";
 
   meta = with lib.maintainers; {
     maintainers = [ illustris ];
@@ -8,7 +8,7 @@ import ./make-test-python.nix ({ pkgs, lib, package ? pkgs.hbase, ... }:
 
   nodes = {
     hbase = { pkgs, ... }: {
-      services.hbase = {
+      services.hbase-standalone = {
         enable = true;
         inherit package;
         # Needed for standalone mode in hbase 2+
diff --git a/nixos/tests/headscale.nix b/nixos/tests/headscale.nix
new file mode 100644
index 000000000000..48658b5dade4
--- /dev/null
+++ b/nixos/tests/headscale.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "headscale";
+  meta.maintainers = with lib.maintainers; [ misterio77 ];
+
+  nodes.machine = { ... }: {
+    services.headscale.enable = true;
+    environment.systemPackages = [ pkgs.headscale ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("headscale")
+    machine.wait_for_open_port(8080)
+    # Test basic funcionality
+    machine.succeed("headscale namespaces create test")
+    machine.succeed("headscale preauthkeys -n test create")
+  '';
+})
diff --git a/nixos/tests/hedgedoc.nix b/nixos/tests/hedgedoc.nix
index 657d49c555e9..410350d83627 100644
--- a/nixos/tests/hedgedoc.nix
+++ b/nixos/tests/hedgedoc.nix
@@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       services = {
         hedgedoc = {
           enable = true;
-          configuration.dbURL = "sqlite:///var/lib/hedgedoc/hedgedoc.db";
+          settings.dbURL = "sqlite:///var/lib/hedgedoc/hedgedoc.db";
         };
       };
     };
@@ -21,7 +21,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       services = {
         hedgedoc = {
           enable = true;
-          configuration.dbURL = "postgres://hedgedoc:\${DB_PASSWORD}@localhost:5432/hedgedocdb";
+          settings.dbURL = "postgres://hedgedoc:\${DB_PASSWORD}@localhost:5432/hedgedocdb";
 
           /*
            * Do not use pkgs.writeText for secrets as
diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix
index 7a4b331169a3..cb75322ca5f9 100644
--- a/nixos/tests/hibernate.nix
+++ b/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";
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 10f9cb05c9cb..8d58de75eabc 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -7,8 +7,6 @@ in {
   meta.maintainers = lib.teams.home-assistant.members;
 
   nodes.hass = { pkgs, ... }: {
-    environment.systemPackages = with pkgs; [ mosquitto ];
-
     services.postgresql = {
       enable = true;
       ensureDatabases = [ "hass" ];
@@ -98,22 +96,51 @@ in {
       };
       lovelaceConfigWritable = true;
     };
+
+    # Cause a configuration change inside `configuration.yml` and verify that the process is being reloaded.
+    specialisation.differentName = {
+      inheritParentConfig = true;
+      configuration.services.home-assistant.config.homeassistant.name = lib.mkForce "Test Home";
+    };
+
+    # 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 = {};
+    };
   };
 
-  testScript = ''
+  testScript = { nodes, ... }: let
+    system = nodes.hass.config.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,
+    # access the final package, that is overridden 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")
+        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'")
+
+
     hass.wait_for_unit("home-assistant.service")
+    cursor = get_journal_cursor(hass)
 
     with subtest("Check that YAML configuration file is in place"):
         hass.succeed("test -L ${configDir}/configuration.yaml")
@@ -130,7 +157,7 @@ in {
         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"):
-        hass.wait_until_succeeds("journalctl -u home-assistant.service | grep -q 'Home Assistant initialized in'")
+        wait_for_homeassistant(hass, cursor)
         hass.wait_for_open_port(8123)
         hass.succeed("curl --fail http://localhost:8123/lovelace")
 
@@ -141,12 +168,25 @@ in {
     with subtest("Check extra components are considered in systemd unit hardening"):
         hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
 
-    with subtest("Print log to ease debugging"):
-        output_log = hass.succeed("cat ${configDir}/home-assistant.log")
-        print("\n### home-assistant.log ###\n")
-        print(output_log + "\n")
+    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)
 
     with subtest("Check that no errors were logged"):
+        output_log = hass.succeed("cat ${configDir}/home-assistant.log")
         assert "ERROR" not in output_log
 
     with subtest("Check systemd unit hardening"):
diff --git a/nixos/tests/hydra/common.nix b/nixos/tests/hydra/common.nix
index fdf2b2c6f6dc..2bce03418e1f 100644
--- a/nixos/tests/hydra/common.nix
+++ b/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/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
index 9fc787842d85..baf18afbc569 100644
--- a/nixos/tests/hydra/default.nix
+++ b/nixos/tests/hydra/default.nix
@@ -11,7 +11,7 @@ let
   inherit (import ./common.nix { inherit system; }) baseConfig;
 
   hydraPkgs = {
-    inherit (pkgs) hydra-unstable;
+    inherit (pkgs) hydra_unstable;
   };
 
   makeHydraTest = with pkgs.lib; name: package: makeTest {
diff --git a/nixos/tests/ihatemoney/default.nix b/nixos/tests/ihatemoney/default.nix
index cd5f073343da..894a97d43d35 100644
--- a/nixos/tests/ihatemoney/default.nix
+++ b/nixos/tests/ihatemoney/default.nix
@@ -32,14 +32,7 @@ let
         };
       };
       # ihatemoney needs a local smtp server otherwise project creation just crashes
-      services.opensmtpd = {
-        enable = true;
-        serverConfiguration = ''
-          listen on lo
-          action foo relay
-          match from any for any action foo
-        '';
-      };
+      services.postfix.enable = true;
     };
     testScript = ''
       machine.wait_for_open_port(8000)
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index fd16b481168f..78a6325a245e 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/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'"
               )
             '';
         }
@@ -92,20 +92,20 @@ in
   fwupd = callInstalledTest ./fwupd.nix {};
   gcab = callInstalledTest ./gcab.nix {};
   gdk-pixbuf = callInstalledTest ./gdk-pixbuf.nix {};
+  geocode-glib = callInstalledTest ./geocode-glib.nix {};
   gjs = callInstalledTest ./gjs.nix {};
   glib-networking = callInstalledTest ./glib-networking.nix {};
   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 {};
   malcontent = callInstalledTest ./malcontent.nix {};
   ostree = callInstalledTest ./ostree.nix {};
   pipewire = callInstalledTest ./pipewire.nix {};
-  power-profiles-daemon = callInstalledTest ./power-profiles-daemon.nix {};
   xdg-desktop-portal = callInstalledTest ./xdg-desktop-portal.nix {};
 }
diff --git a/nixos/tests/installed-tests/flatpak-builder.nix b/nixos/tests/installed-tests/flatpak-builder.nix
index 31b9f2b258fd..d5e04fcf975c 100644
--- a/nixos/tests/installed-tests/flatpak-builder.nix
+++ b/nixos/tests/installed-tests/flatpak-builder.nix
@@ -6,9 +6,10 @@ makeInstalledTest {
   testConfig = {
     services.flatpak.enable = true;
     xdg.portal.enable = true;
+    xdg.portal.extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
     environment.systemPackages = with pkgs; [ flatpak-builder ] ++ flatpak-builder.installedTestsDependencies;
     virtualisation.diskSize = 2048;
   };
 
-  testRunnerFlags = "--timeout 3600";
+  testRunnerFlags = [ "--timeout" "3600" ];
 }
diff --git a/nixos/tests/installed-tests/flatpak.nix b/nixos/tests/installed-tests/flatpak.nix
index c7fe9cf45882..9524d890c402 100644
--- a/nixos/tests/installed-tests/flatpak.nix
+++ b/nixos/tests/installed-tests/flatpak.nix
@@ -13,5 +13,5 @@ makeInstalledTest {
     virtualisation.diskSize = 3072;
   };
 
-  testRunnerFlags = "--timeout 3600";
+  testRunnerFlags = [ "--timeout" "3600" ];
 }
diff --git a/nixos/tests/installed-tests/gdk-pixbuf.nix b/nixos/tests/installed-tests/gdk-pixbuf.nix
index 3d0011a427a4..110efdbf710f 100644
--- a/nixos/tests/installed-tests/gdk-pixbuf.nix
+++ b/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/nixos/tests/installed-tests/geocode-glib.nix b/nixos/tests/installed-tests/geocode-glib.nix
new file mode 100644
index 000000000000..fcb38c96ab0f
--- /dev/null
+++ b/nixos/tests/installed-tests/geocode-glib.nix
@@ -0,0 +1,13 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  testConfig = {
+    i18n.supportedLocales = [
+      "en_US.UTF-8/UTF-8"
+      # The tests require this locale available.
+      "en_GB.UTF-8/UTF-8"
+    ];
+  };
+
+  tested = pkgs.geocode-glib;
+}
diff --git a/nixos/tests/installed-tests/ibus.nix b/nixos/tests/installed-tests/ibus.nix
index a4bc2a7d7de0..028c20c29f2d 100644
--- a/nixos/tests/installed-tests/ibus.nix
+++ b/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/nixos/tests/installed-tests/json-glib.nix b/nixos/tests/installed-tests/json-glib.nix
new file mode 100644
index 000000000000..3dfd3dd0b098
--- /dev/null
+++ b/nixos/tests/installed-tests/json-glib.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.json-glib;
+}
diff --git a/nixos/tests/installed-tests/librsvg.nix b/nixos/tests/installed-tests/librsvg.nix
deleted file mode 100644
index 378e7cce3ff4..000000000000
--- a/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/nixos/tests/installed-tests/power-profiles-daemon.nix b/nixos/tests/installed-tests/power-profiles-daemon.nix
deleted file mode 100644
index 43629a0155d2..000000000000
--- a/nixos/tests/installed-tests/power-profiles-daemon.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-{ pkgs, lib, makeInstalledTest, ... }:
-
-makeInstalledTest {
-  tested = pkgs.power-profiles-daemon;
-
-  testConfig = {
-    services.power-profiles-daemon.enable = true;
-  };
-}
diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix
new file mode 100644
index 000000000000..03f0ec8d746b
--- /dev/null
+++ b/nixos/tests/installer-systemd-stage-1.nix
@@ -0,0 +1,34 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+{
+  # Some of these tests don't work with systemd stage 1 yet. Uncomment
+  # them when fixed.
+  inherit (import ./installer.nix { inherit system config pkgs; systemdStage1 = true; })
+    # bcache
+    btrfsSimple
+    btrfsSubvolDefault
+    btrfsSubvolEscape
+    btrfsSubvols
+    # encryptedFSWithKeyfile
+    # grub1
+    # luksroot
+    # luksroot-format1
+    # luksroot-format2
+    # lvm
+    separateBoot
+    separateBootFat
+    simple
+    simpleLabels
+    simpleProvided
+    simpleSpecialised
+    simpleUefiGrub
+    simpleUefiGrubSpecialisation
+    simpleUefiSystemdBoot
+    # swraid
+    zfsroot
+    ;
+
+}
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index ea2b2d04ed19..398ad8de19cf 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -1,6 +1,7 @@
 { system ? builtins.currentSystem,
   config ? {},
-  pkgs ? import ../.. { inherit system config; }
+  pkgs ? import ../.. { inherit system config; },
+  systemdStage1 ? false
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
@@ -23,6 +24,8 @@ let
         # 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) ''
@@ -54,7 +57,7 @@ let
 
         hardware.enableAllFirmware = lib.mkForce false;
 
-        ${replaceChars ["\n"] ["\n  "] extraConfig}
+        ${replaceStrings ["\n"] ["\n  "] extraConfig}
       }
     '';
 
@@ -290,6 +293,8 @@ let
           virtualisation.cores = 8;
           virtualisation.memorySize = 1536;
 
+          boot.initrd.systemd.enable = systemdStage1;
+
           # Use a small /dev/vdb as the root disk for the
           # installer. This ensures the target disk (/dev/vda) is
           # the same during and after installation.
@@ -319,6 +324,9 @@ let
             desktop-file-utils
             docbook5
             docbook_xsl_ns
+            (docbook-xsl-ns.override {
+              withManOptDedupPatch = true;
+            })
             kmod.dev
             libarchive.dev
             libxml2.bin
@@ -328,6 +336,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
@@ -696,6 +711,85 @@ in {
     '';
   };
 
+  bcachefsSimple = makeInstallerTest "bcachefs-simple" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+    };
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root /dev/vda3",
+        "mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  bcachefsEncrypted = makeInstallerTest "bcachefs-encrypted" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+      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";
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "keyctl link @u @s",
+        "echo password | mkfs.bcachefs -L root --encrypted /dev/vda3",
+        "echo password | bcachefs unlock /dev/vda3",
+        "mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  bcachefsMulti = makeInstallerTest "bcachefs-multi" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+    };
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M 4096M"            # /
+        + " mkpart primary 4096M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root --metadata_replicas 2 --foreground_target ssd --promote_target ssd --background_target hdd --label ssd /dev/vda3 --label hdd /dev/vda4",
+        "mount -t bcachefs /dev/vda3:/dev/vda4 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
   # Test a basic install using GRUB 1.
   grub1 = makeInstallerTest "grub1" rec {
     createPartitions = ''
@@ -817,4 +911,25 @@ 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",
+      )
+    '';
+  };
 }
diff --git a/nixos/tests/invoiceplane.nix b/nixos/tests/invoiceplane.nix
index 4e63f8ac21c9..70ed96ee39f3 100644
--- a/nixos/tests/invoiceplane.nix
+++ b/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/nixos/tests/isso.nix b/nixos/tests/isso.nix
index 65bae5f5dced..575e1c52eccf 100644
--- a/nixos/tests/isso.nix
+++ b/nixos/tests/isso.nix
@@ -22,7 +22,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   ''
     machine.wait_for_unit("isso.service")
 
-    machine.wait_for_open_port("${toString port}")
+    machine.wait_for_open_port(${toString port})
 
     machine.succeed("curl --fail http://localhost:${toString port}/?uri")
     machine.succeed("curl --fail http://localhost:${toString port}/js/embed.min.js")
diff --git a/nixos/tests/jellyfin.nix b/nixos/tests/jellyfin.nix
index 4ac378699637..7d3097b58629 100644
--- a/nixos/tests/jellyfin.nix
+++ b/nixos/tests/jellyfin.nix
@@ -52,18 +52,18 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
             machine.succeed(api_post("/Startup/Complete"))
 
         with machine.nested("Can login"):
-            auth_result = machine.succeed(
+            auth_result_str = machine.succeed(
                 api_post(
                     "/Users/AuthenticateByName",
                     "${payloads.auth}",
                 )
             )
-            auth_result = json.loads(auth_result)
+            auth_result = json.loads(auth_result_str)
             auth_token = auth_result["AccessToken"]
             auth_header += f", Token={auth_token}"
 
-            sessions_result = machine.succeed(api_get("/Sessions"))
-            sessions_result = json.loads(sessions_result)
+            sessions_result_str = machine.succeed(api_get("/Sessions"))
+            sessions_result = json.loads(sessions_result_str)
 
             this_session = [
                 session for session in sessions_result if session["DeviceId"] == "1337"
@@ -71,8 +71,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
             if len(this_session) != 1:
                 raise Exception("Session not created")
 
-            me = machine.succeed(api_get("/Users/Me"))
-            me = json.loads(me)["Id"]
+            me_str = machine.succeed(api_get("/Users/Me"))
+            me = json.loads(me_str)["Id"]
 
         with machine.nested("Can add library"):
             tempdir = machine.succeed("mktemp -d -p /var/lib/jellyfin").strip()
@@ -100,8 +100,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
 
 
         def is_refreshed(_):
-            folders = machine.succeed(api_get("/Library/VirtualFolders"))
-            folders = json.loads(folders)
+            folders_str = machine.succeed(api_get("/Library/VirtualFolders"))
+            folders = json.loads(folders_str)
             print(folders)
             return all(folder["RefreshStatus"] == "Idle" for folder in folders)
 
@@ -116,10 +116,10 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
             def has_movie(_):
                 global items
 
-                items = machine.succeed(
+                items_str = machine.succeed(
                     api_get(f"/Users/{me}/Items?IncludeItemTypes=Movie&Recursive=true")
                 )
-                items = json.loads(items)["Items"]
+                items = json.loads(items_str)["Items"]
 
                 return len(items) == 1
 
@@ -127,8 +127,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
 
             video = items[0]["Id"]
 
-            item_info = machine.succeed(api_get(f"/Users/{me}/Items/{video}"))
-            item_info = json.loads(item_info)
+            item_info_str = machine.succeed(api_get(f"/Users/{me}/Items/{video}"))
+            item_info = json.loads(item_info_str)
 
             if item_info["Name"] != "Big Buck Bunny":
                 raise Exception("Jellyfin failed to properly identify file")
diff --git a/nixos/tests/jenkins.nix b/nixos/tests/jenkins.nix
index 265d1a330cd9..a1ede6dc917b 100644
--- a/nixos/tests/jenkins.nix
+++ b/nixos/tests/jenkins.nix
@@ -79,7 +79,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     in ''
     start_all()
 
-    master.wait_for_unit("jenkins")
+    master.wait_for_unit("default.target")
 
     assert "Authentication required" in master.succeed("curl http://localhost:8080")
 
@@ -94,18 +94,12 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     with subtest("jobs are declarative"):
         # Check that jobs are created on disk.
-        master.wait_for_unit("jenkins-job-builder")
-        master.wait_until_fails("systemctl is-active jenkins-job-builder")
         master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/job-1/config.xml")
         master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/folder-1/config.xml")
         master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/folder-1/jobs/job-2/config.xml")
 
-        # Wait until jenkins is ready, reload configuration and verify it also
-        # sees the jobs.
-        master.succeed("curl --fail ${jenkinsUrl}/cli")
-        master.succeed("curl ${jenkinsUrl}/jnlpJars/jenkins-cli.jar -O")
-        master.succeed("${pkgs.jre}/bin/java -jar jenkins-cli.jar -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) reload-configuration")
-        out = master.succeed("${pkgs.jre}/bin/java -jar jenkins-cli.jar -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) list-jobs")
+        # Verify that jenkins also sees the jobs.
+        out = master.succeed("${pkgs.jenkins}/bin/jenkins-cli -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) list-jobs")
         jobs = [x.strip() for x in out.splitlines()]
         # Seeing jobs inside folders requires the Folders plugin
         # (https://plugins.jenkins.io/cloudbees-folder/), which we don't have
@@ -117,15 +111,12 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         )
 
         # Check that jobs are removed from disk.
-        master.wait_for_unit("jenkins-job-builder")
-        master.wait_until_fails("systemctl is-active jenkins-job-builder")
         master.wait_until_fails("test -f /var/lib/jenkins/jobs/job-1/config.xml")
         master.wait_until_fails("test -f /var/lib/jenkins/jobs/folder-1/config.xml")
         master.wait_until_fails("test -f /var/lib/jenkins/jobs/folder-1/jobs/job-2/config.xml")
 
-        # Reload jenkins' configuration and verify it also sees the jobs as removed.
-        master.succeed("${pkgs.jre}/bin/java -jar jenkins-cli.jar -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) reload-configuration")
-        out = master.succeed("${pkgs.jre}/bin/java -jar jenkins-cli.jar -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) list-jobs")
+        # Verify that jenkins also sees the jobs as removed.
+        out = master.succeed("${pkgs.jenkins}/bin/jenkins-cli -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) list-jobs")
         jobs = [x.strip() for x in out.splitlines()]
         assert jobs == [], f"jobs != []: {jobs}"
   '';
diff --git a/nixos/tests/jibri.nix b/nixos/tests/jibri.nix
index 223120cdb229..45e30af9a9a5 100644
--- a/nixos/tests/jibri.nix
+++ b/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/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix
index 41d53bc73800..c39cd19e1f0a 100644
--- a/nixos/tests/jitsi-meet.nix
+++ b/nixos/tests/jitsi-meet.nix
@@ -34,9 +34,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     server.wait_for_unit("prosody.service")
 
     server.wait_until_succeeds(
-        "journalctl -b -u jitsi-videobridge2 -o cat | grep -q 'Performed a successful health check'"
-    )
-    server.wait_until_succeeds(
         "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.server'"
     )
     server.wait_until_succeeds(
diff --git a/nixos/tests/k3s-single-node-docker.nix b/nixos/tests/k3s-single-node-docker.nix
deleted file mode 100644
index 735aa5ac2975..000000000000
--- a/nixos/tests/k3s-single-node-docker.nix
+++ /dev/null
@@ -1,84 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
-  let
-    imageEnv = pkgs.buildEnv {
-      name = "k3s-pause-image-env";
-      paths = with pkgs; [ tini (hiPrio coreutils) busybox ];
-    };
-    pauseImage = pkgs.dockerTools.streamLayeredImage {
-      name = "test.local/pause";
-      tag = "local";
-      contents = imageEnv;
-      config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ];
-    };
-    # Don't use the default service account because there's a race where it may
-    # not be created yet; make our own instead.
-    testPodYaml = pkgs.writeText "test.yml" ''
-      apiVersion: v1
-      kind: ServiceAccount
-      metadata:
-        name: test
-      ---
-      apiVersion: v1
-      kind: Pod
-      metadata:
-        name: test
-      spec:
-        serviceAccountName: test
-        containers:
-        - name: test
-          image: test.local/pause:local
-          imagePullPolicy: Never
-          command: ["sh", "-c", "sleep inf"]
-    '';
-  in
-  {
-    name = "k3s";
-    meta = with pkgs.lib.maintainers; {
-      maintainers = [ euank ];
-    };
-
-    nodes.machine = { pkgs, ... }: {
-      environment.systemPackages = with pkgs; [ k3s gzip ];
-
-      # k3s uses enough resources the default vm fails.
-      virtualisation.memorySize = 1536;
-      virtualisation.diskSize = 4096;
-
-      services.k3s = {
-        enable = true;
-        role = "server";
-        docker = true;
-        # Slightly reduce resource usage
-        extraFlags = "--no-deploy coredns,servicelb,traefik,local-storage,metrics-server --pause-image test.local/pause:local";
-      };
-
-      users.users = {
-        noprivs = {
-          isNormalUser = true;
-          description = "Can't access k3s by default";
-          password = "*";
-        };
-      };
-    };
-
-    testScript = ''
-      start_all()
-
-      machine.wait_for_unit("k3s")
-      machine.succeed("k3s kubectl cluster-info")
-      machine.fail("sudo -u noprivs k3s kubectl cluster-info")
-      # FIXME: this fails with the current nixos kernel config; once it passes, we should uncomment it
-      # machine.succeed("k3s check-config")
-
-      machine.succeed(
-          "${pauseImage} | docker load"
-      )
-
-      machine.succeed("k3s kubectl apply -f ${testPodYaml}")
-      machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test")
-      machine.succeed("k3s kubectl delete -f ${testPodYaml}")
-
-      machine.shutdown()
-    '';
-  })
diff --git a/nixos/tests/k3s/default.nix b/nixos/tests/k3s/default.nix
new file mode 100644
index 000000000000..07d93c41c7a6
--- /dev/null
+++ b/nixos/tests/k3s/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+{
+  # Run a single node k3s cluster and verify a pod can run
+  single-node = import ./single-node.nix { inherit system pkgs; };
+  # Run a multi-node k3s cluster and verify pod networking works across nodes
+  multi-node = import ./multi-node.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/k3s/multi-node.nix b/nixos/tests/k3s/multi-node.nix
new file mode 100644
index 000000000000..9a6c7fd46573
--- /dev/null
+++ b/nixos/tests/k3s/multi-node.nix
@@ -0,0 +1,183 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    imageEnv = pkgs.buildEnv {
+      name = "k3s-pause-image-env";
+      paths = with pkgs; [ tini bashInteractive coreutils socat ];
+    };
+    pauseImage = pkgs.dockerTools.streamLayeredImage {
+      name = "test.local/pause";
+      tag = "local";
+      contents = imageEnv;
+      config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ];
+    };
+    # A daemonset that responds 'server' on port 8000
+    networkTestDaemonset = pkgs.writeText "test.yml" ''
+      apiVersion: apps/v1
+      kind: DaemonSet
+      metadata:
+        name: test
+        labels:
+          name: test
+      spec:
+        selector:
+          matchLabels:
+            name: test
+        template:
+          metadata:
+            labels:
+              name: test
+          spec:
+            containers:
+            - name: test
+              image: test.local/pause:local
+              imagePullPolicy: Never
+              resources:
+                limits:
+                  memory: 20Mi
+              command: ["socat", "TCP4-LISTEN:8000,fork", "EXEC:echo server"]
+    '';
+    tokenFile = pkgs.writeText "token" "p@s$w0rd";
+  in
+  {
+    name = "k3s-multi-node";
+
+    nodes = {
+      server = { pkgs, ... }: {
+        environment.systemPackages = with pkgs; [ gzip jq ];
+        # k3s uses enough resources the default vm fails.
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          role = "server";
+          package = pkgs.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 = [ 2379 2380 6443 ];
+        networking.firewall.allowedUDPPorts = [ 8472 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.1";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+
+      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;
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          role = "agent";
+          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 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.2";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+    };
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ euank ];
+    };
+
+    testScript = ''
+      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:
+          # 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 -"
+          )
+
+      server.succeed("k3s kubectl cluster-info")
+      # Also wait for our service account to show up; it takes a sec
+      server.wait_until_succeeds("k3s kubectl get serviceaccount default")
+
+      # Now create a pod on each node via a daemonset and verify they can talk to each other.
+      server.succeed("k3s kubectl apply -f ${networkTestDaemonset}")
+      server.wait_until_succeeds(f'[ "$(k3s kubectl get ds test -o json | jq .status.numberReady)" -eq {len(machines)} ]')
+
+      # Get pod IPs
+      pods = server.succeed("k3s kubectl get po -o json | jq '.items[].metadata.name' -r").splitlines()
+      pod_ips = [server.succeed(f"k3s kubectl get po {name} -o json | jq '.status.podIP' -cr").strip() for name in pods]
+
+      # Verify each server can ping each pod ip
+      for pod_ip in pod_ips:
+          server.succeed(f"ping -c 1 {pod_ip}")
+          agent.succeed(f"ping -c 1 {pod_ip}")
+
+      # Verify the pods can talk to each other
+      resp = server.wait_until_succeeds(f"k3s kubectl exec {pods[0]} -- socat TCP:{pod_ips[1]}:8000 -")
+      assert resp.strip() == "server"
+      resp = server.wait_until_succeeds(f"k3s kubectl exec {pods[1]} -- socat TCP:{pod_ips[0]}:8000 -")
+      assert resp.strip() == "server"
+
+      # Cleanup
+      server.succeed("k3s kubectl delete -f ${networkTestDaemonset}")
+
+      for m in machines:
+          m.shutdown()
+    '';
+  })
diff --git a/nixos/tests/k3s-single-node.nix b/nixos/tests/k3s/single-node.nix
index fb6510ee087b..a95fa4a031e3 100644
--- a/nixos/tests/k3s-single-node.nix
+++ b/nixos/tests/k3s/single-node.nix
@@ -1,5 +1,4 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
+import ../make-test-python.nix ({ pkgs, lib, ... }:
   let
     imageEnv = pkgs.buildEnv {
       name = "k3s-pause-image-env";
@@ -11,20 +10,12 @@ import ./make-test-python.nix ({ pkgs, ... }:
       contents = imageEnv;
       config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ];
     };
-    # Don't use the default service account because there's a race where it may
-    # not be created yet; make our own instead.
     testPodYaml = pkgs.writeText "test.yml" ''
       apiVersion: v1
-      kind: ServiceAccount
-      metadata:
-        name: test
-      ---
-      apiVersion: v1
       kind: Pod
       metadata:
         name: test
       spec:
-        serviceAccountName: test
         containers:
         - name: test
           image: test.local/pause:local
@@ -49,7 +40,14 @@ import ./make-test-python.nix ({ pkgs, ... }:
       services.k3s.role = "server";
       services.k3s.package = pkgs.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 = {
@@ -66,13 +64,15 @@ 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")
-      # FIXME: this fails with the current nixos kernel config; once it passes, we should uncomment it
-      # 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 -"
       )
 
+      # Also wait for our service account to show up; it takes a sec
+      machine.wait_until_succeeds("k3s kubectl get serviceaccount default")
       machine.succeed("k3s kubectl apply -f ${testPodYaml}")
       machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test")
       machine.succeed("k3s kubectl delete -f ${testPodYaml}")
diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix
index 5def759ca24d..79af02710c32 100644
--- a/nixos/tests/kafka.nix
+++ b/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/nixos/tests/kanidm.nix b/nixos/tests/kanidm.nix
new file mode 100644
index 000000000000..33c65026b9b1
--- /dev/null
+++ b/nixos/tests/kanidm.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    serverDomain = certs.domain;
+  in
+  {
+    name = "kanidm";
+    meta.maintainers = with pkgs.lib.maintainers; [ erictapen Flakebi ];
+
+    nodes.server = { config, pkgs, lib, ... }: {
+      services.kanidm = {
+        enableServer = true;
+        serverSettings = {
+          origin = "https://${serverDomain}";
+          domain = serverDomain;
+          bindaddress = "[::]:443";
+          ldapbindaddress = "[::1]:636";
+          tls_chain = certs."${serverDomain}".cert;
+          tls_key = certs."${serverDomain}".key;
+        };
+      };
+
+      security.pki.certificateFiles = [ certs.ca.cert ];
+
+      networking.hosts."::1" = [ serverDomain ];
+      networking.firewall.allowedTCPPorts = [ 443 ];
+
+      users.users.kanidm.shell = pkgs.bashInteractive;
+
+      environment.systemPackages = with pkgs; [ kanidm openldap ripgrep ];
+    };
+
+    nodes.client = { pkgs, nodes, ... }: {
+      services.kanidm = {
+        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 ];
+
+      security.pki.certificateFiles = [ certs.ca.cert ];
+    };
+
+    testScript = { nodes, ... }:
+      let
+        ldapBaseDN = builtins.concatStringsSep "," (map (s: "dc=" + s) (pkgs.lib.splitString "." serverDomain));
+
+        # 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;
+        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.succeed("ldapsearch -H ldaps://${serverDomain}:636 -b '${ldapBaseDN}' -x '(name=test)'")
+        client.succeed("kanidm login -D anonymous && kanidm self whoami | grep anonymous@${serverDomain}")
+        rv, result = server.execute("kanidmd recover_account -c ${serverConfigFile} idm_admin 2>&1 | rg -o '[A-Za-z0-9]{48}'")
+        assert rv == 0
+        client.wait_for_unit("kanidm-unixd.service")
+        client.succeed("kanidm_unixd_status | grep working!")
+      '';
+  })
diff --git a/nixos/tests/karma.nix b/nixos/tests/karma.nix
new file mode 100644
index 000000000000..5ac2983b8aa3
--- /dev/null
+++ b/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/nixos/tests/kea.nix b/nixos/tests/kea.nix
index 6b345893108f..b1d5894cc7cd 100644
--- a/nixos/tests/kea.nix
+++ b/nixos/tests/kea.nix
@@ -1,6 +1,8 @@
 import ./make-test-python.nix ({ pkgs, lib, ...}: {
   meta.maintainers = with lib.maintainers; [ hexa ];
 
+  name = "kea";
+
   nodes = {
     router = { config, pkgs, ... }: {
       virtualisation.vlans = [ 1 ];
diff --git a/nixos/tests/keepassxc.nix b/nixos/tests/keepassxc.nix
index d0f353c71e0b..303be1330405 100644
--- a/nixos/tests/keepassxc.nix
+++ b/nixos/tests/keepassxc.nix
@@ -62,7 +62,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
         machine.send_key("tab")
         machine.send_chars("/home/alice/foo.keyfile")
         machine.send_key("ret")
-        # Passwords folder is displayed
-        machine.wait_for_text("Passwords")
+        # Database is unlocked (doesn't have "[Locked]" in the title anymore)
+        machine.wait_for_text("foo.kdbx - KeePassXC")
   '';
 })
diff --git a/nixos/tests/kerberos/mit.nix b/nixos/tests/kerberos/mit.nix
index b475b7e4c92b..7e427ffef0ba 100644
--- a/nixos/tests/kerberos/mit.nix
+++ b/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/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index f34d5d607940..7ee734a1eff0 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -30,6 +30,7 @@ let
       linux_5_4_hardened
       linux_5_10_hardened
       linux_5_15_hardened
+      linux_6_0_hardened
 
       linux_testing;
   };
diff --git a/nixos/tests/keter.nix b/nixos/tests/keter.nix
new file mode 100644
index 000000000000..0bfb96e1c324
--- /dev/null
+++ b/nixos/tests/keter.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  port = 81;
+in
+{
+  name = "keter";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ jappie ];
+  };
+
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.keter = {
+      enable = true;
+
+      globalKeterConfig = {
+        listeners = [{
+          host = "*4";
+          inherit port;
+        }];
+      };
+      bundle = {
+        appName = "test-bundle";
+        domain = "localhost";
+        executable = pkgs.writeShellScript "run" ''
+          ${pkgs.python3}/bin/python -m http.server $PORT
+        '';
+      };
+    };
+  };
+
+  testScript =
+    ''
+      machine.wait_for_unit("keter.service")
+
+      machine.wait_for_open_port(${toString port})
+      machine.wait_for_console_text("Activating app test-bundle with hosts: localhost")
+
+
+      machine.succeed("curl --fail http://localhost:${toString port}/")
+    '';
+})
diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix
index 7238a9f58e09..3f5a6f521af0 100644
--- a/nixos/tests/kexec.nix
+++ b/nixos/tests/kexec.nix
@@ -18,8 +18,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       virtualisation.vlans = [ ];
       environment.systemPackages = [ pkgs.hello ];
       imports = [
-        "${modulesPath}/installer/kexec/kexec-boot.nix"
-        "${modulesPath}/profiles/minimal.nix"
+        "${modulesPath}/installer/netboot/netboot-minimal.nix"
       ];
     };
   };
@@ -33,14 +32,14 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     node1.connect()
     node1.wait_for_unit("multi-user.target")
 
-    # Check if the machine with kexec-boot.nix profile boots up
+    # Check if the machine with netboot-minimal.nix profile boots up
     node2.wait_for_unit("multi-user.target")
     node2.shutdown()
 
     # Kexec node1 to the toplevel of node2 via the kexec-boot script
     node1.succeed('touch /run/foo')
     node1.fail('hello')
-    node1.execute('${nodes.node2.config.system.build.kexecBoot}/kexec-boot', check_return=False)
+    node1.execute('${nodes.node2.config.system.build.kexecTree}/kexec-boot', check_return=False)
     node1.succeed('! test -e /run/foo')
     node1.succeed('hello')
     node1.succeed('[ "$(hostname)" = "node2" ]')
diff --git a/nixos/tests/keycloak.nix b/nixos/tests/keycloak.nix
index 6ce136330d43..228e57d1cdd6 100644
--- a/nixos/tests/keycloak.nix
+++ b/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/nixos/tests/komga.nix b/nixos/tests/komga.nix
new file mode 100644
index 000000000000..02db50ef25f7
--- /dev/null
+++ b/nixos/tests/komga.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "komga";
+  meta.maintainers = with maintainers; [ govanify ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.komga = {
+        enable = true;
+        port = 1234;
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("komga.service")
+    machine.wait_for_open_port(1234)
+    machine.succeed("curl --fail http://localhost:1234/")
+  '';
+})
diff --git a/nixos/tests/krb5/example-config.nix b/nixos/tests/krb5/example-config.nix
index 1125b02f01ca..9a5c3b2af249 100644
--- a/nixos/tests/krb5/example-config.nix
+++ b/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/nixos/tests/kthxbye.nix b/nixos/tests/kthxbye.nix
new file mode 100644
index 000000000000..5ca0917ec8e7
--- /dev/null
+++ b/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/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix
index d4410beb937e..ba7b2d9b1d2d 100644
--- a/nixos/tests/kubernetes/base.nix
+++ b/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/nixos/tests/kubernetes/default.nix b/nixos/tests/kubernetes/default.nix
index 60ba482758fb..a3de9ed115d4 100644
--- a/nixos/tests/kubernetes/default.nix
+++ b/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/nixos/tests/kubernetes/dns.nix b/nixos/tests/kubernetes/dns.nix
index 3fd1dd31f746..1b7145eb5d5e 100644
--- a/nixos/tests/kubernetes/dns.nix
+++ b/nixos/tests/kubernetes/dns.nix
@@ -33,7 +33,11 @@ let
   redisImage = pkgs.dockerTools.buildImage {
     name = "redis";
     tag = "latest";
-    contents = [ pkgs.redis pkgs.bind.host ];
+    copyToRoot = pkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ pkgs.redis pkgs.bind.host ];
+    };
     config.Entrypoint = ["/bin/redis-server"];
   };
 
@@ -54,14 +58,18 @@ let
   probeImage = pkgs.dockerTools.buildImage {
     name = "probe";
     tag = "latest";
-    contents = [ pkgs.bind.host pkgs.busybox ];
+    copyToRoot = pkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ pkgs.bind.host pkgs.busybox ];
+    };
     config.Entrypoint = ["/bin/tail"];
   };
 
   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/nixos/tests/kubernetes/e2e.nix b/nixos/tests/kubernetes/e2e.nix
deleted file mode 100644
index fb29d9cc6953..000000000000
--- a/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/nixos/tests/kubernetes/rbac.nix b/nixos/tests/kubernetes/rbac.nix
index 9e73fbbd32a8..779eafbb1d24 100644
--- a/nixos/tests/kubernetes/rbac.nix
+++ b/nixos/tests/kubernetes/rbac.nix
@@ -84,7 +84,11 @@ let
   kubectlImage = pkgs.dockerTools.buildImage {
     name = "kubectl";
     tag = "latest";
-    contents = [ copyKubectl pkgs.busybox kubectlPod2 ];
+    copyToRoot = pkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ copyKubectl pkgs.busybox kubectlPod2 ];
+    };
     config.Entrypoint = ["/bin/sh"];
   };
 
diff --git a/nixos/tests/ipfs.nix b/nixos/tests/kubo.nix
index 5e7c967028e4..94aa24a9204f 100644
--- a/nixos/tests/ipfs.nix
+++ b/nixos/tests/kubo.nix
@@ -1,19 +1,27 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
-  name = "ipfs";
+  name = "kubo";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ mguentner ];
   };
 
   nodes.machine = { ... }: {
-    services.ipfs = {
+    services.kubo = {
       enable = true;
       # Also will add a unix domain socket socket API address, see module.
       startWhenNeeded = true;
-      apiAddress = "/ip4/127.0.0.1/tcp/2324";
+      settings.Addresses.API = "/ip4/127.0.0.1/tcp/2324";
       dataDir = "/mnt/ipfs";
     };
   };
 
+  nodes.fuse = { ... }: {
+    services.kubo = {
+      enable = true;
+      settings.Addresses.API = "/ip4/127.0.0.1/tcp/2324";
+      autoMount = true;
+    };
+  };
+
   testScript = ''
     start_all()
 
@@ -40,5 +48,14 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     # 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/nixos/tests/ladybird.nix b/nixos/tests/ladybird.nix
new file mode 100644
index 000000000000..4e9ab9a36d13
--- /dev/null
+++ b/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/nixos/tests/languagetool.nix b/nixos/tests/languagetool.nix
new file mode 100644
index 000000000000..e4ab2a47064e
--- /dev/null
+++ b/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/nixos/tests/lemmy.nix b/nixos/tests/lemmy.nix
new file mode 100644
index 000000000000..fb64daa80e64
--- /dev/null
+++ b/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/nixos/tests/libreddit.nix b/nixos/tests/libreddit.nix
index f7ef701d0865..82a44cb4e9cb 100644
--- a/nixos/tests/libreddit.nix
+++ b/nixos/tests/libreddit.nix
@@ -6,14 +6,16 @@ with lib;
   name = "libreddit";
   meta.maintainers = with maintainers; [ fab ];
 
-  nodes.machine =
-    { pkgs, ... }:
-    { services.libreddit.enable = true; };
+  nodes.machine = {
+    services.libreddit.enable = true;
+    # Test CAP_NET_BIND_SERVICE
+    services.libreddit.port = 80;
+  };
 
   testScript = ''
     machine.wait_for_unit("libreddit.service")
-    machine.wait_for_open_port("8080")
-    # The service wants to get data from https://www.reddit.com
-    machine.succeed("curl http://localhost:8080/")
+    machine.wait_for_open_port(80)
+    # Query a page that does not require Internet access
+    machine.succeed("curl --fail http://localhost:80/settings")
   '';
 })
diff --git a/nixos/tests/libuiohook.nix b/nixos/tests/libuiohook.nix
new file mode 100644
index 000000000000..66c5033d9688
--- /dev/null
+++ b/nixos/tests/libuiohook.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "libuiohook";
+  meta = with lib.maintainers; { maintainers = [ anoa ]; };
+
+  nodes.client = { nodes, ... }:
+      let user = nodes.client.config.users.users.alice;
+      in {
+        imports = [ ./common/user-account.nix ./common/x11.nix ];
+
+        environment.systemPackages = [ pkgs.libuiohook.test ];
+
+        test-support.displayManager.auto.user = user.name;
+      };
+
+  testScript = { nodes, ... }:
+    let user = nodes.client.config.users.users.alice;
+    in ''
+      client.wait_for_x()
+      client.succeed("su - alice -c ${pkgs.libuiohook.test}/share/uiohook_tests >&2 &")
+    '';
+})
diff --git a/nixos/tests/libvirtd.nix b/nixos/tests/libvirtd.nix
new file mode 100644
index 000000000000..49258fcb93ea
--- /dev/null
+++ b/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.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/nixos/tests/lidarr.nix b/nixos/tests/lidarr.nix
index d3f83e5d9145..7fbaea62f80e 100644
--- a/nixos/tests/lidarr.nix
+++ b/nixos/tests/lidarr.nix
@@ -14,7 +14,7 @@ with lib;
     start_all()
 
     machine.wait_for_unit("lidarr.service")
-    machine.wait_for_open_port("8686")
+    machine.wait_for_open_port(8686)
     machine.succeed("curl --fail http://localhost:8686/")
   '';
 })
diff --git a/nixos/tests/lighttpd.nix b/nixos/tests/lighttpd.nix
new file mode 100644
index 000000000000..36e2745c55c1
--- /dev/null
+++ b/nixos/tests/lighttpd.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "lighttpd";
+  meta.maintainers = with lib.maintainers; [ bjornfor ];
+
+  nodes = {
+    server = {
+      services.lighttpd.enable = true;
+      services.lighttpd.document-root = pkgs.runCommand "document-root" {} ''
+        mkdir -p "$out"
+        echo "hello nixos test" > "$out/file.txt"
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("lighttpd.service")
+    res = server.succeed("curl --fail http://localhost/file.txt")
+    assert "hello nixos test" in res, f"bad server response: '{res}'"
+  '';
+})
diff --git a/nixos/tests/listmonk.nix b/nixos/tests/listmonk.nix
new file mode 100644
index 000000000000..91003653c09e
--- /dev/null
+++ b/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/nixos/tests/login.nix b/nixos/tests/login.nix
index 0d6f81b17219..2cff38d20059 100644
--- a/nixos/tests/login.nix
+++ b/nixos/tests/login.nix
@@ -29,11 +29,11 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
           machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
 
       with subtest("Log in as alice on a virtual console"):
-          machine.wait_until_tty_matches(2, "login: ")
+          machine.wait_until_tty_matches("2", "login: ")
           machine.send_chars("alice\n")
-          machine.wait_until_tty_matches(2, "login: alice")
+          machine.wait_until_tty_matches("2", "login: alice")
           machine.wait_until_succeeds("pgrep login")
-          machine.wait_until_tty_matches(2, "Password: ")
+          machine.wait_until_tty_matches("2", "Password: ")
           machine.send_chars("foobar\n")
           machine.wait_until_succeeds("pgrep -u alice bash")
           machine.send_chars("touch done\n")
diff --git a/nixos/tests/logrotate.nix b/nixos/tests/logrotate.nix
index b0685f3af9ff..94f6ad5103fb 100644
--- a/nixos/tests/logrotate.nix
+++ b/nixos/tests/logrotate.nix
@@ -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/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix
index 209b87f9f26a..a4bdc92490ce 100644
--- a/nixos/tests/lorri/default.nix
+++ b/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/nixos/tests/lxd-image-server.nix b/nixos/tests/lxd-image-server.nix
index fa40e33e74dd..e5a292b61bd9 100644
--- a/nixos/tests/lxd-image-server.nix
+++ b/nixos/tests/lxd-image-server.nix
@@ -1,54 +1,21 @@
-import ./make-test-python.nix ({ pkgs, ...} :
+import ./make-test-python.nix ({ pkgs, lib, ... } :
 
 let
-  # Since we don't have access to the internet during the tests, we have to
-  # pre-fetch lxd containers beforehand.
-  #
-  # I've chosen to import Alpine Linux, because its image is turbo-tiny and,
-  # generally, sufficient for our tests.
-  alpine-meta = pkgs.fetchurl {
-    url = "https://tarballs.nixos.org/alpine/3.12/lxd.tar.xz";
-    hash = "sha256-1tcKaO9lOkvqfmG/7FMbfAEToAuFy2YMewS8ysBKuLA=";
-  };
-
-  alpine-rootfs = pkgs.fetchurl {
-    url = "https://tarballs.nixos.org/alpine/3.12/rootfs.tar.xz";
-    hash = "sha256-Tba9sSoaiMtQLY45u7p5DMqXTSDgs/763L/SQp0bkCA=";
+  lxd-image = import ../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+    };
   };
 
-  lxd-config = pkgs.writeText "config.yaml" ''
-    storage_pools:
-      - name: default
-        driver: dir
-        config:
-          source: /var/lxd-pool
-
-    networks:
-      - name: lxdbr0
-        type: bridge
-        config:
-          ipv4.address: auto
-          ipv6.address: none
-
-    profiles:
-      - name: default
-        devices:
-          eth0:
-            name: eth0
-            network: lxdbr0
-            type: nic
-          root:
-            path: /
-            pool: default
-            type: disk
-  '';
-
+  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";
 
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ mkg20001 ];
+    maintainers = [ mkg20001 patryk27 ];
   };
 
   nodes.machine = { lib, ... }: {
@@ -100,20 +67,20 @@ in {
     # lxd expects the pool's directory to already exist
     machine.succeed("mkdir /var/lxd-pool")
 
-
     machine.succeed(
-        "cat ${lxd-config} | lxd init --preseed"
+        "cat ${./common/lxd/config.yaml} | lxd init --preseed"
     )
 
     machine.succeed(
-        "lxc image import ${alpine-meta} ${alpine-rootfs} --alias alpine"
+        "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs}/*/*.tar.xz --alias nixos"
     )
 
-    loc = "/var/www/simplestreams/images/iats/alpine/amd64/default/v1"
+    loc = "/var/www/simplestreams/images/iats/nixos/amd64/default/v1"
 
     with subtest("push image to server"):
-        machine.succeed("lxc launch alpine test")
-        machine.succeed("lxc stop test")
+        machine.succeed("lxc launch nixos test")
+        machine.sleep(5)
+        machine.succeed("lxc stop -f test")
         machine.succeed("lxc publish --public test --alias=testimg")
         machine.succeed("lxc image export testimg")
         machine.succeed("ls >&2")
diff --git a/nixos/tests/lxd-image.nix b/nixos/tests/lxd-image.nix
deleted file mode 100644
index 4930b55f1909..000000000000
--- a/nixos/tests/lxd-image.nix
+++ /dev/null
@@ -1,89 +0,0 @@
-# This test ensures that the nixOS lxd images builds and functions properly
-# It has been extracted from `lxd.nix` to seperate failures of just the image and the lxd software
-
-import ./make-test-python.nix ({ pkgs, ...} : let
-  release = import ../release.nix {
-    /* configuration = {
-      environment.systemPackages = with pkgs; [ stdenv ]; # inject stdenv so rebuild test works
-    }; */
-  };
-
-  metadata = release.lxdMeta.${pkgs.system};
-  image = release.lxdImage.${pkgs.system};
-
-  lxd-config = pkgs.writeText "config.yaml" ''
-    storage_pools:
-      - name: default
-        driver: dir
-        config:
-          source: /var/lxd-pool
-
-    networks:
-      - name: lxdbr0
-        type: bridge
-        config:
-          ipv4.address: auto
-          ipv6.address: none
-
-    profiles:
-      - name: default
-        devices:
-          eth0:
-            name: eth0
-            network: lxdbr0
-            type: nic
-          root:
-            path: /
-            pool: default
-            type: disk
-  '';
-in {
-  name = "lxd-image";
-
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ mkg20001 ];
-  };
-
-  nodes.machine = { lib, ... }: {
-    virtualisation = {
-      # disk full otherwise
-      diskSize = 2048;
-
-      lxc.lxcfs.enable = true;
-      lxd.enable = true;
-    };
-  };
-
-  testScript = ''
-    machine.wait_for_unit("sockets.target")
-    machine.wait_for_unit("lxd.service")
-    machine.wait_for_file("/var/lib/lxd/unix.socket")
-
-    # It takes additional second for lxd to settle
-    machine.sleep(1)
-
-    # lxd expects the pool's directory to already exist
-    machine.succeed("mkdir /var/lxd-pool")
-
-    machine.succeed(
-        "cat ${lxd-config} | lxd init --preseed"
-    )
-
-    # TODO: test custom built container aswell
-
-    with subtest("importing container works"):
-        machine.succeed("lxc image import ${metadata}/*/*.tar.xz ${image}/*/*.tar.xz --alias nixos")
-
-    with subtest("launching container works"):
-        machine.succeed("lxc launch nixos machine -c security.nesting=true")
-        # make sure machine boots up properly
-        machine.sleep(5)
-
-    with subtest("container shell works"):
-        machine.succeed("echo true | lxc exec machine /run/current-system/sw/bin/bash -")
-        machine.succeed("lxc exec machine /run/current-system/sw/bin/true")
-
-    # with subtest("rebuilding works"):
-    #     machine.succeed("lxc exec machine /run/current-system/sw/bin/nixos-rebuild switch")
-  '';
-})
diff --git a/nixos/tests/lxd.nix b/nixos/tests/lxd.nix
index 81b36124cc6b..2c2c19e0eecf 100644
--- a/nixos/tests/lxd.nix
+++ b/nixos/tests/lxd.nix
@@ -1,48 +1,18 @@
-import ./make-test-python.nix ({ pkgs, ...} :
+import ./make-test-python.nix ({ pkgs, lib, ... } :
 
 let
-  # Since we don't have access to the internet during the tests, we have to
-  # pre-fetch lxd containers beforehand.
-  #
-  # I've chosen to import Alpine Linux, because its image is turbo-tiny and,
-  # generally, sufficient for our tests.
-  alpine-meta = pkgs.fetchurl {
-    url = "https://tarballs.nixos.org/alpine/3.12/lxd.tar.xz";
-    hash = "sha256-1tcKaO9lOkvqfmG/7FMbfAEToAuFy2YMewS8ysBKuLA=";
-  };
+  lxd-image = import ../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
 
-  alpine-rootfs = pkgs.fetchurl {
-    url = "https://tarballs.nixos.org/alpine/3.12/rootfs.tar.xz";
-    hash = "sha256-Tba9sSoaiMtQLY45u7p5DMqXTSDgs/763L/SQp0bkCA=";
+      # Our tests require `grep` & friends:
+      environment.systemPackages = with pkgs; [ busybox ];
+    };
   };
 
-  lxd-config = pkgs.writeText "config.yaml" ''
-    storage_pools:
-      - name: default
-        driver: dir
-        config:
-          source: /var/lxd-pool
-
-    networks:
-      - name: lxdbr0
-        type: bridge
-        config:
-          ipv4.address: auto
-          ipv6.address: none
-
-    profiles:
-      - name: default
-        devices:
-          eth0:
-            name: eth0
-            network: lxdbr0
-            type: nic
-          root:
-            path: /
-            pool: default
-            type: disk
-  '';
-
+  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.stdenv.hostPlatform.system};
 
 in {
   name = "lxd";
@@ -53,6 +23,8 @@ in {
 
   nodes.machine = { lib, ... }: {
     virtualisation = {
+      diskSize = 4096;
+
       # Since we're testing `limits.cpu`, we've gotta have a known number of
       # cores to lean on
       cores = 2;
@@ -77,61 +49,66 @@ in {
     machine.succeed("mkdir /var/lxd-pool")
 
     machine.succeed(
-        "cat ${lxd-config} | lxd init --preseed"
+        "cat ${./common/lxd/config.yaml} | lxd init --preseed"
     )
 
     machine.succeed(
-        "lxc image import ${alpine-meta} ${alpine-rootfs} --alias alpine"
+        "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs}/*/*.tar.xz --alias nixos"
     )
 
-    with subtest("Containers can be launched and destroyed"):
-        machine.succeed("lxc launch alpine test")
-        machine.succeed("lxc exec test true")
-        machine.succeed("lxc delete -f test")
+    with subtest("Container can be managed"):
+        machine.succeed("lxc launch nixos container")
+        machine.sleep(5)
+        machine.succeed("echo true | lxc exec container /run/current-system/sw/bin/bash -")
+        machine.succeed("lxc exec container true")
+        machine.succeed("lxc delete -f container")
 
-    with subtest("Containers are being mounted with lxcfs inside"):
-        machine.succeed("lxc launch alpine test")
+    with subtest("Container is mounted with lxcfs inside"):
+        machine.succeed("lxc launch nixos container")
+        machine.sleep(5)
 
         ## ---------- ##
         ## limits.cpu ##
 
-        machine.succeed("lxc config set test limits.cpu 1")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.cpu 1")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
-        # Since Alpine doesn't have `nproc` pre-installed, we've gotta resort
-        # to the primal methods
         assert (
             "1"
-            == machine.succeed("lxc exec test grep -- -c ^processor /proc/cpuinfo").strip()
+            == machine.succeed("lxc exec container grep -- -c ^processor /proc/cpuinfo").strip()
         )
 
-        machine.succeed("lxc config set test limits.cpu 2")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.cpu 2")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
         assert (
             "2"
-            == machine.succeed("lxc exec test grep -- -c ^processor /proc/cpuinfo").strip()
+            == machine.succeed("lxc exec container grep -- -c ^processor /proc/cpuinfo").strip()
         )
 
         ## ------------- ##
         ## limits.memory ##
 
-        machine.succeed("lxc config set test limits.memory 64MB")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.memory 64MB")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
         assert (
             "MemTotal:          62500 kB"
-            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+            == machine.succeed("lxc exec container grep -- MemTotal /proc/meminfo").strip()
         )
 
-        machine.succeed("lxc config set test limits.memory 128MB")
-        machine.succeed("lxc restart test")
+        machine.succeed("lxc config set container limits.memory 128MB")
+        machine.succeed("lxc restart container")
+        machine.sleep(5)
 
         assert (
             "MemTotal:         125000 kB"
-            == machine.succeed("lxc exec test grep -- MemTotal /proc/meminfo").strip()
+            == machine.succeed("lxc exec container grep -- MemTotal /proc/meminfo").strip()
         )
 
-        machine.succeed("lxc delete -f test")
+        machine.succeed("lxc delete -f container")
   '';
 })
diff --git a/nixos/tests/maddy.nix b/nixos/tests/maddy.nix
index 581748c1fa59..b9d0416482da 100644
--- a/nixos/tests/maddy.nix
+++ b/nixos/tests/maddy.nix
@@ -49,7 +49,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     server.wait_for_open_port(143)
     server.wait_for_open_port(587)
 
-    server.succeed("echo test | maddyctl creds create postmaster@server")
+    server.succeed("maddyctl creds create --password test postmaster@server")
     server.succeed("maddyctl imap-acct create postmaster@server")
 
     client.succeed("send-testmail")
diff --git a/nixos/tests/mailcatcher.nix b/nixos/tests/mailcatcher.nix
index f23b749a021e..627ef56617e9 100644
--- a/nixos/tests/mailcatcher.nix
+++ b/nixos/tests/mailcatcher.nix
@@ -24,7 +24,7 @@ import ./make-test-python.nix ({ lib, ... }:
     start_all()
 
     machine.wait_for_unit("mailcatcher.service")
-    machine.wait_for_open_port("1025")
+    machine.wait_for_open_port(1025)
     machine.succeed(
         'echo "this is the body of the email" | mail -s "subject" root@example.org'
     )
diff --git a/nixos/tests/mailhog.nix b/nixos/tests/mailhog.nix
index 3508c2c0a5ea..e3c2da37a3c8 100644
--- a/nixos/tests/mailhog.nix
+++ b/nixos/tests/mailhog.nix
@@ -12,8 +12,8 @@ import ./make-test-python.nix ({ lib, ... }: {
     start_all()
 
     machine.wait_for_unit("mailhog.service")
-    machine.wait_for_open_port("1025")
-    machine.wait_for_open_port("8025")
+    machine.wait_for_open_port(1025)
+    machine.wait_for_open_port(8025)
     machine.succeed(
         'echo "this is the body of the email" | swaks --to root@example.org --body - --server localhost:1025'
     )
diff --git a/nixos/tests/make-test-python.nix b/nixos/tests/make-test-python.nix
index 7a96f538d8d7..28569f1d2955 100644
--- a/nixos/tests/make-test-python.nix
+++ b/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/nixos/tests/matomo.nix b/nixos/tests/matomo.nix
index 526a24fc4db7..0e09ad295f95 100644
--- a/nixos/tests/matomo.nix
+++ b/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/nixos/tests/matrix-appservice-irc.nix b/nixos/tests/matrix/appservice-irc.nix
index d1c561f95dbf..78c53024ca6c 100644
--- a/nixos/tests/matrix-appservice-irc.nix
+++ b/nixos/tests/matrix/appservice-irc.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ pkgs, ... }:
+import ../make-test-python.nix ({ pkgs, ... }:
   let
     homeserverUrl = "http://homeserver:8008";
   in
@@ -20,6 +20,9 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
               enable_registration = true;
 
+              # don't use this in production, always use some form of verification
+              enable_registration_without_verification = true;
+
               listeners = [ {
                 # The default but tls=false
                 bind_addresses = [
@@ -190,6 +193,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
     testScript = ''
       import pathlib
+      import os
 
       start_all()
 
@@ -203,7 +207,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       with subtest("copy the registration file"):
           appservice.copy_from_vm("/var/lib/matrix-appservice-irc/registration.yml")
           homeserver.copy_from_host(
-              pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml", "/"
+              str(pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml"), "/"
           )
           homeserver.succeed("chmod 444 /registration.yml")
 
diff --git a/nixos/tests/matrix-conduit.nix b/nixos/tests/matrix/conduit.nix
index d159fbaa4800..2b81c23598eb 100644
--- a/nixos/tests/matrix-conduit.nix
+++ b/nixos/tests/matrix/conduit.nix
@@ -1,8 +1,10 @@
-import ./make-test-python.nix ({ pkgs, ... }:
+import ../make-test-python.nix ({ pkgs, ... }:
   let
     name = "conduit";
   in
   {
+    name = "matrix-conduit";
+
     nodes = {
       conduit = args: {
         services.matrix-conduit = {
diff --git a/nixos/tests/dendrite.nix b/nixos/tests/matrix/dendrite.nix
index a444c9b20018..82e71d912130 100644
--- a/nixos/tests/dendrite.nix
+++ b/nixos/tests/matrix/dendrite.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix (
+import ../make-test-python.nix (
   { pkgs, ... }:
     let
       homeserverUrl = "http://homeserver:8008";
@@ -17,9 +17,11 @@ import ./make-test-python.nix (
           homeserver = { pkgs, ... }: {
             services.dendrite = {
               enable = true;
+              loadCredential = [ "test_private_key:${private_key}" ];
+              openRegistration = true;
               settings = {
                 global.server_name = "test-dendrite-server.com";
-                global.private_key = private_key;
+                global.private_key = "$CREDENTIALS_DIRECTORY/test_private_key";
                 client_api.registration_disabled = false;
               };
             };
diff --git a/nixos/tests/matrix/mjolnir.nix b/nixos/tests/matrix/mjolnir.nix
index 54094ab9d611..b1ac55d951ce 100644
--- a/nixos/tests/matrix/mjolnir.nix
+++ b/nixos/tests/matrix/mjolnir.nix
@@ -32,6 +32,7 @@ import ../make-test-python.nix (
     name = "mjolnir";
     meta = with pkgs.lib; {
       maintainers = teams.matrix.members;
+      broken = true; # times out after spending many hours
     };
 
     nodes = {
@@ -43,6 +44,7 @@ import ../make-test-python.nix (
             tls_certificate_path = "${cert}";
             tls_private_key_path = "${key}";
             enable_registration = true;
+            enable_registration_without_verification = true;
             registration_shared_secret = "supersecret-registration";
 
             listeners = [ {
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix/synapse.nix
index 1ff1e47b2840..698d67c793e3 100644
--- a/nixos/tests/matrix-synapse.nix
+++ b/nixos/tests/matrix/synapse.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ pkgs, ... } : let
+import ../make-test-python.nix ({ pkgs, ... } : let
 
 
   runWithOpenSSL = file: cmd: pkgs.runCommand file {
@@ -27,7 +27,7 @@ import ./make-test-python.nix ({ pkgs, ... } : let
   '';
 
 
-  mailerCerts = import ./common/acme/server/snakeoil-certs.nix;
+  mailerCerts = import ../common/acme/server/snakeoil-certs.nix;
   mailerDomain = mailerCerts.domain;
   registrationSharedSecret = "unsecure123";
   testUser = "alice";
@@ -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/nixos/tests/mediatomb.nix b/nixos/tests/mediatomb.nix
index b7a126a01ad5..9c84aa3e92a5 100644
--- a/nixos/tests/mediatomb.nix
+++ b/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/nixos/tests/meilisearch.nix b/nixos/tests/meilisearch.nix
index 9f54aa97d6ad..c31dcb0559db 100644
--- a/nixos/tests/meilisearch.nix
+++ b/nixos/tests/meilisearch.nix
@@ -5,9 +5,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
     apiUrl = "http://${listenAddress}:${toString listenPort}";
     uid = "movies";
     indexJSON = pkgs.writeText "index.json" (builtins.toJSON { inherit uid; });
-    moviesJSON = pkgs.runCommand "movies.json" {} ''
-      sed -n '1,5p;$p' ${pkgs.meilisearch.src}/datasets/movies/movies.json > $out
-    '';
+    moviesJSON = pkgs.fetchurl {
+      url = "https://github.com/meilisearch/meilisearch/raw/v0.23.1/datasets/movies/movies.json";
+      sha256 = "1r3srld63dpmg9yrmysm6xl175661j5cspi93mk5q2wf8xwn50c5";
+    };
   in {
     name = "meilisearch";
     meta.maintainers = with lib.maintainers; [ Br1ght0ne ];
@@ -26,7 +27,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       start_all()
 
       machine.wait_for_unit("meilisearch")
-      machine.wait_for_open_port("7700")
+      machine.wait_for_open_port(7700)
 
       with subtest("check version"):
           version = json.loads(machine.succeed("curl ${apiUrl}/version"))
@@ -34,20 +35,20 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
       with subtest("create index"):
           machine.succeed(
-              "curl -XPOST ${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 ${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/nixos/tests/merecat.nix b/nixos/tests/merecat.nix
new file mode 100644
index 000000000000..9d8f66165ee9
--- /dev/null
+++ b/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/nixos/tests/mimir.nix b/nixos/tests/mimir.nix
new file mode 100644
index 000000000000..f1b30d261472
--- /dev/null
+++ b/nixos/tests/mimir.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "mimir";
+  nodes = {
+    server = { ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      services.mimir.enable = true;
+      services.mimir.configuration = {
+        ingester.ring.replication_factor = 1;
+      };
+
+      services.telegraf.enable = true;
+      services.telegraf.extraConfig = {
+        agent.interval = "1s";
+        agent.flush_interval = "1s";
+        inputs.exec = {
+          commands = [
+            "${pkgs.coreutils}/bin/echo 'foo i=42i'"
+          ];
+          data_format = "influx";
+        };
+        outputs = {
+          http = {
+            # test remote write
+            url = "http://localhost:8080/api/v1/push";
+
+            # Data format to output.
+            data_format = "prometheusremotewrite";
+
+            headers = {
+              Content-Type = "application/x-protobuf";
+              Content-Encoding = "snappy";
+              X-Scope-OrgID = "nixos";
+              X-Prometheus-Remote-Write-Version = "0.1.0";
+            };
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("mimir.service")
+    server.wait_for_unit("telegraf.service")
+    server.wait_for_open_port(8080)
+    server.wait_until_succeeds(
+        "curl -H 'X-Scope-OrgID: nixos' http://127.0.0.1:8080/prometheus/api/v1/label/host/values | jq -r '.data[0]' | grep server"
+    )
+  '';
+})
diff --git a/nixos/tests/minecraft-server.nix b/nixos/tests/minecraft-server.nix
index dbe2cd6d56fe..6e733bb96c1c 100644
--- a/nixos/tests/minecraft-server.nix
+++ b/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;
@@ -33,5 +35,6 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
     assert "${seed}" in server.succeed(
         "mcrcon -H localhost -P ${toString rcon-port} -p '${rcon-pass}' -c 'seed'"
     )
+    server.succeed("systemctl stop minecraft-server")
   '';
 })
diff --git a/nixos/tests/minidlna.nix b/nixos/tests/minidlna.nix
index 104b79078fd5..32721819634e 100644
--- a/nixos/tests/minidlna.nix
+++ b/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 =
@@ -32,7 +31,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     start_all()
     server.succeed("mkdir -p /tmp/stuff && chown minidlna: /tmp/stuff")
     server.wait_for_unit("minidlna")
-    server.wait_for_open_port("8200")
+    server.wait_for_open_port(8200)
     # requests must be made *by IP* to avoid triggering minidlna's
     # DNS-rebinding protection
     server.succeed("curl --fail http://$(getent ahostsv4 localhost | head -n1 | cut -f 1 -d ' '):8200/")
diff --git a/nixos/tests/moodle.nix b/nixos/tests/moodle.nix
index 4570e8963882..8fd011e0cb21 100644
--- a/nixos/tests/moodle.nix
+++ b/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/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix
index 36cc8e3e3d9b..70eecc89278b 100644
--- a/nixos/tests/mosquitto.nix
+++ b/nixos/tests/mosquitto.nix
@@ -4,6 +4,7 @@ let
   port = 1888;
   tlsPort = 1889;
   anonPort = 1890;
+  bindTestPort = 18910;
   password = "VERY_secret";
   hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw==";
   topic = "test/foo";
@@ -125,6 +126,10 @@ in {
               };
             };
           }
+          {
+            settings.bind_interface = "eth0";
+            port = bindTestPort;
+          }
         ];
       };
     };
@@ -134,6 +139,8 @@ in {
   };
 
   testScript = ''
+    import json
+
     def mosquitto_cmd(binary, user, topic, port):
         return (
             "mosquitto_{} "
@@ -158,10 +165,35 @@ 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")
 
+    with subtest("bind_interface"):
+        addrs = dict()
+        for iface in json.loads(server.succeed("ip -json address show")):
+            for addr in iface['addr_info']:
+                # don't want to deal with multihoming here
+                assert addr['local'] not in addrs
+                addrs[addr['local']] = (iface['ifname'], addr['family'])
+
+        # mosquitto grabs *one* random address per type for bind_interface
+        (has4, has6) = (False, False)
+        for line in server.succeed("ss -HlptnO sport = ${toString bindTestPort}").splitlines():
+            items = line.split()
+            if "mosquitto" not in items[5]: continue
+            listener = items[3].rsplit(':', maxsplit=1)[0].strip('[]')
+            assert listener in addrs
+            assert addrs[listener][0] == "eth0"
+            has4 |= addrs[listener][1] == 'inet'
+            has6 |= addrs[listener][1] == 'inet6'
+        assert has4
+        assert has6
+
     with subtest("check passwords"):
         client1.succeed(publish("-m test", "password_store"))
         client1.succeed(publish("-m test", "password_file"))
@@ -175,14 +207,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"))
             ])
 
@@ -201,7 +233,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/nixos/tests/musescore.nix b/nixos/tests/musescore.nix
index 18de0a550239..ac2f4ba74c0f 100644
--- a/nixos/tests/musescore.nix
+++ b/nixos/tests/musescore.nix
@@ -69,6 +69,10 @@ in
     # Wait until the export dialogue appears.
     machine.wait_for_window("Export")
     machine.screenshot("MuseScore1")
+    machine.send_key("shift-tab")
+    machine.sleep(1)
+    machine.send_key("shift-tab")
+    machine.sleep(1)
     machine.send_key("ret")
     machine.sleep(1)
     machine.send_key("ret")
diff --git a/nixos/tests/mysql/common.nix b/nixos/tests/mysql/common.nix
index 040d360b6d99..7fdf0f33d3f3 100644
--- a/nixos/tests/mysql/common.nix
+++ b/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/nixos/tests/mysql/mysql-backup.nix b/nixos/tests/mysql/mysql-backup.nix
index 9335b233327a..968f56dd3c9b 100644
--- a/nixos/tests/mysql/mysql-backup.nix
+++ b/nixos/tests/mysql/mysql-backup.nix
@@ -51,7 +51,6 @@ let
 
       # Do a backup and wait for it to start
       master.start_job("mysql-backup.service")
-      master.wait_for_unit("mysql-backup.service")
 
       # wait for backup to fail, because of database 'doesnotexist'
       master.wait_until_fails("systemctl is-active -q mysql-backup.service")
diff --git a/nixos/tests/n8n.nix b/nixos/tests/n8n.nix
index ed93639f2a42..c1753a418f67 100644
--- a/nixos/tests/n8n.nix
+++ b/nixos/tests/n8n.nix
@@ -7,7 +7,7 @@ let
 in
 {
   name = "n8n";
-  meta.maintainers = with maintainers; [ freezeboy ];
+  meta.maintainers = with maintainers; [ freezeboy k900 ];
 
   nodes.machine =
     { pkgs, ... }:
@@ -19,7 +19,7 @@ in
 
   testScript = ''
     machine.wait_for_unit("n8n.service")
-    machine.wait_for_open_port("${toString port}")
+    machine.wait_for_open_port(${toString port})
     machine.succeed("curl --fail http://localhost:${toString port}/")
   '';
 })
diff --git a/nixos/tests/navidrome.nix b/nixos/tests/navidrome.nix
index 62290d50fc7e..7315aef62401 100644
--- a/nixos/tests/navidrome.nix
+++ b/nixos/tests/navidrome.nix
@@ -7,6 +7,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = ''
     machine.wait_for_unit("navidrome")
-    machine.wait_for_open_port("4533")
+    machine.wait_for_open_port(4533)
   '';
 })
diff --git a/nixos/tests/ncdns.nix b/nixos/tests/ncdns.nix
index 5099d697e035..3ce39ed3cb55 100644
--- a/nixos/tests/ncdns.nix
+++ b/nixos/tests/ncdns.nix
@@ -73,14 +73,14 @@ in
 
       with subtest("DNSKEY bit record is present"):
           server.wait_for_unit("pdns-recursor")
-          server.wait_for_open_port("53")
+          server.wait_for_open_port(53)
           server.succeed("host -t DNSKEY bit")
     '') +
     ''
       with subtest("can resolve a .bit name"):
           server.wait_for_unit("namecoind")
           server.wait_for_unit("ncdns")
-          server.wait_for_open_port("8332")
+          server.wait_for_open_port(8332)
           assert "1.2.3.4" in server.succeed("dig @localhost -p 5333 test.bit")
 
       with subtest("SOA record has identity information"):
diff --git a/nixos/tests/neo4j.nix b/nixos/tests/neo4j.nix
index 8329e5630d7a..0b57f5b2e038 100644
--- a/nixos/tests/neo4j.nix
+++ b/nixos/tests/neo4j.nix
@@ -2,19 +2,25 @@ import ./make-test-python.nix {
   name = "neo4j";
 
   nodes = {
-    master =
+    server =
       { ... }:
 
       {
+        virtualisation.memorySize = 4096;
+        virtualisation.diskSize = 1024;
+
         services.neo4j.enable = true;
+        # require tls certs to be available
+        services.neo4j.https.enable = false;
+        services.neo4j.bolt.enable = false;
       };
   };
 
   testScript = ''
     start_all()
 
-    master.wait_for_unit("neo4j")
-    master.wait_for_open_port(7474)
-    master.succeed("curl -f http://localhost:7474/")
+    server.wait_for_unit("neo4j.service")
+    server.wait_for_open_port(7474)
+    server.succeed("curl -f http://localhost:7474/")
   '';
 }
diff --git a/nixos/tests/netbird.nix b/nixos/tests/netbird.nix
new file mode 100644
index 000000000000..ef793cfe9881
--- /dev/null
+++ b/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/nixos/tests/networking-proxy.nix b/nixos/tests/networking-proxy.nix
index fcb2558cf3b0..330bac2588a5 100644
--- a/nixos/tests/networking-proxy.nix
+++ b/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/nixos/tests/networking.nix b/nixos/tests/networking.nix
index a1150097a091..71b82b871270 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -77,12 +77,14 @@ let
   testCases = {
     loopback = {
       name = "Loopback";
-      machine.networking.useDHCP = false;
-      machine.networking.useNetworkd = networkd;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        networking.useDHCP = false;
+        networking.useNetworkd = networkd;
+      };
       testScript = ''
         start_all()
-        machine.wait_for_unit("network.target")
-        loopback_addresses = machine.succeed("ip addr show lo")
+        client.wait_for_unit("network.target")
+        loopback_addresses = client.succeed("ip addr show lo")
         assert "inet 127.0.0.1/8" in loopback_addresses
         assert "inet6 ::1/128" in loopback_addresses
       '';
@@ -96,6 +98,7 @@ let
           useNetworkd = networkd;
           useDHCP = false;
           defaultGateway = "192.168.1.1";
+          defaultGateway6 = "fd00:1234:5678:1::1";
           interfaces.eth1.ipv4.addresses = mkOverride 0 [
             { address = "192.168.1.2"; prefixLength = 24; }
             { address = "192.168.1.3"; prefixLength = 32; }
@@ -137,8 +140,49 @@ let
           with subtest("Test default gateway"):
               router.wait_until_succeeds("ping -c 1 192.168.3.1")
               client.wait_until_succeeds("ping -c 1 192.168.3.1")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
         '';
     };
+    routeType = {
+      name = "RouteType";
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        networking = {
+          useDHCP = false;
+          useNetworkd = networkd;
+          interfaces.eth1.ipv4.routes = [{
+            address = "192.168.1.127";
+            prefixLength = 32;
+            type = "local";
+          }];
+        };
+      };
+      testScript = ''
+        start_all()
+        client.wait_for_unit("network.target")
+        client.succeed("ip -4 route list table local | grep 'local 192.168.1.127'")
+      '';
+    };
+    dhcpDefault = {
+      name = "useDHCP-by-default";
+      nodes.router = router;
+      nodes.client = { lib, ... }: {
+        # Disable test driver default config
+        networking.interfaces = lib.mkForce {};
+        networking.useNetworkd = networkd;
+        virtualisation.vlans = [ 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.shell_interact()
+        client.succeed("ping -c 1 192.168.1.1")
+        router.succeed("ping -c 1 192.168.1.1")
+        router.succeed("ping -c 1 192.168.1.2")
+        client.succeed("ping -c 1 192.168.1.2")
+      '';
+    };
     dhcpSimple = {
       name = "SimpleDHCP";
       nodes.router = router;
@@ -638,6 +682,46 @@ let
               client2.succeed("ip addr show dev vlan >&2")
         '';
     };
+    vlan-ping = let
+        baseIP = number: "10.10.10.${number}";
+        vlanIP = number: "10.1.1.${number}";
+        baseInterface = "eth1";
+        vlanInterface = "vlan42";
+        node = number: {pkgs, ... }: with pkgs.lib; {
+          virtualisation.vlans = [ 1 ];
+          networking = {
+            #useNetworkd = networkd;
+            useDHCP = false;
+            vlans.${vlanInterface} = { id = 42; interface = baseInterface; };
+            interfaces.${baseInterface}.ipv4.addresses = mkOverride 0 [{ address = baseIP number; prefixLength = 24; }];
+            interfaces.${vlanInterface}.ipv4.addresses = mkOverride 0 [{ address = vlanIP number; prefixLength = 24; }];
+          };
+        };
+
+        serverNodeNum = "1";
+        clientNodeNum = "2";
+
+    in {
+      name = "vlan-ping";
+      nodes.server = node serverNodeNum;
+      nodes.client = node clientNodeNum;
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to be configured"):
+              server.wait_for_unit("network.target")
+              client.wait_for_unit("network.target")
+
+          with subtest("Test ping on base interface in setup"):
+              client.succeed("ping -I ${baseInterface} -c 1 ${baseIP serverNodeNum}")
+              server.succeed("ping -I ${baseInterface} -c 1 ${baseIP clientNodeNum}")
+
+          with subtest("Test ping on vlan subinterface in setup"):
+              client.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP serverNodeNum}")
+              server.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP clientNodeNum}")
+        '';
+    };
     virtual = {
       name = "Virtual";
       nodes.machine = {
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index eb37470a4c7b..a475049e7b26 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -37,6 +37,8 @@ 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";
@@ -99,6 +101,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 +114,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/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
index b7b1c5c66002..b8d3ba75b51a 100644
--- a/nixos/tests/nextcloud/default.nix
+++ b/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;
@@ -16,6 +20,10 @@ foldl
       inherit system pkgs;
       nextcloudVersion = ver;
     };
+    "with-declarative-redis-and-secrets${toString ver}" = import ./with-declarative-redis-and-secrets.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
   })
 { }
-  [ 22 23 ]
+  [ 24 25 ]
diff --git a/nixos/tests/nextcloud/openssl-sse.nix b/nixos/tests/nextcloud/openssl-sse.nix
new file mode 100644
index 000000000000..7595ee2c67e3
--- /dev/null
+++ b/nixos/tests/nextcloud/openssl-sse.nix
@@ -0,0 +1,105 @@
+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}";
+      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")
+
+    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"):
+        nextcloudwithopenssl1.fail("${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/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
new file mode 100644
index 000000000000..93e655c3056b
--- /dev/null
+++ b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
@@ -0,0 +1,117 @@
+import ../make-test-python.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "custom-admin-username";
+in {
+  name = "nextcloud-with-declarative-redis";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        caching = {
+          apcu = false;
+          redis = true;
+          memcached = false;
+        };
+        config = {
+          dbtype = "pgsql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "/run/postgresql";
+          inherit adminuser;
+          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
+            ${adminpass}
+          '');
+        };
+        secretFile = "/etc/nextcloud-secrets.json";
+
+        extraOptions.redis = {
+          host = "/run/redis/redis.sock";
+          port = 0;
+          dbindex = 0;
+          timeout = 1.5;
+          # password handled via secretfile below
+        };
+        extraOptions.memcache = {
+          local = "\OC\Memcache\Redis";
+          locking = "\OC\Memcache\Redis";
+        };
+      };
+
+      services.redis.servers."nextcloud".enable = true;
+      services.redis.servers."nextcloud".port = 6379;
+
+      systemd.services.nextcloud-setup= {
+        requires = ["postgresql.service"];
+        after = [
+          "postgresql.service"
+        ];
+      };
+
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "nextcloud" ];
+        ensureUsers = [
+          { name = "nextcloud";
+            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
+          }
+        ];
+      };
+
+      # This file is meant to contain secret options which should
+      # not go into the nix store. Here it is just used to set the
+      # databyse type to postgres.
+      environment.etc."nextcloud-secrets.json".text = ''
+        {
+          "redis": {
+            "password": "secret"
+          }
+        }
+      '';
+    };
+  };
+
+  testScript = let
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.runtimeShell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.runtimeShell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    start_all()
+    nextcloud.wait_for_unit("multi-user.target")
+    nextcloud.succeed("curl -sSf http://nextcloud/login")
+    nextcloud.succeed(
+        "${withRcloneEnv} ${copySharedFile}"
+    )
+    client.wait_for_unit("multi-user.target")
+    client.succeed(
+        "${withRcloneEnv} ${diffSharedFile}"
+    )
+
+    # redis cache should not be empty
+    nextcloud.fail("redis-cli KEYS * | grep -q 'empty array'")
+  '';
+})
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 36a69fda505b..1ef848cfb121 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -37,9 +37,8 @@ in {
         };
       };
 
-      services.redis = {
-        enable = true;
-      };
+      services.redis.servers."nextcloud".enable = true;
+      services.redis.servers."nextcloud".port = 6379;
 
       systemd.services.nextcloud-setup= {
         requires = ["postgresql.service"];
diff --git a/nixos/tests/nghttpx.nix b/nixos/tests/nghttpx.nix
index d83c1c4cae63..11cac332827d 100644
--- a/nixos/tests/nghttpx.nix
+++ b/nixos/tests/nghttpx.nix
@@ -54,8 +54,8 @@ in
     testScript = ''
       start_all()
 
-      webserver.wait_for_open_port("80")
-      proxy.wait_for_open_port("80")
+      webserver.wait_for_open_port(80)
+      proxy.wait_for_open_port(80)
       client.wait_until_succeeds("curl -s --fail http://proxy/hello-world.txt")
     '';
   })
diff --git a/nixos/tests/nginx-auth.nix b/nixos/tests/nginx-auth.nix
index c0d24a20ddbc..a85426dda871 100644
--- a/nixos/tests/nginx-auth.nix
+++ b/nixos/tests/nginx-auth.nix
@@ -13,14 +13,14 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
         virtualHosts.lockedroot = {
           inherit root;
-          basicAuth.alice = "jane";
+          basicAuth.alice = "pwofa";
         };
 
         virtualHosts.lockedsubdir = {
           inherit root;
           locations."/sublocation/" = {
             alias = "${root}/";
-            basicAuth.bob = "john";
+            basicAuth.bob = "pwofb";
           };
         };
       };
@@ -33,7 +33,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     webserver.fail("curl --fail --resolve lockedroot:80:127.0.0.1 http://lockedroot")
     webserver.succeed(
-        "curl --fail --resolve lockedroot:80:127.0.0.1 http://alice:jane@lockedroot"
+        "curl --fail --resolve lockedroot:80:127.0.0.1 http://alice:pwofa@lockedroot"
     )
 
     webserver.succeed("curl --fail --resolve lockedsubdir:80:127.0.0.1 http://lockedsubdir")
@@ -41,7 +41,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         "curl --fail --resolve lockedsubdir:80:127.0.0.1 http://lockedsubdir/sublocation/index.html"
     )
     webserver.succeed(
-        "curl --fail --resolve lockedsubdir:80:127.0.0.1 http://bob:john@lockedsubdir/sublocation/index.html"
+        "curl --fail --resolve lockedsubdir:80:127.0.0.1 http://bob:pwofb@lockedsubdir/sublocation/index.html"
     )
   '';
 })
diff --git a/nixos/tests/nginx-etag.nix b/nixos/tests/nginx-etag.nix
index b69511d081d4..6f45eacf8b41 100644
--- a/nixos/tests/nginx-etag.nix
+++ b/nixos/tests/nginx-etag.nix
@@ -53,14 +53,14 @@ import ./make-test-python.nix {
 
           driver.implicitly_wait(20)
           driver.get('http://server/')
-          driver.find_element_by_xpath('//div[@foo="bar"]')
+          driver.find_element('xpath', '//div[@foo="bar"]')
           open('/tmp/passed_stage1', 'w')
 
           while not os.path.exists('/tmp/proceed'):
               time.sleep(0.5)
 
           driver.get('http://server/')
-          driver.find_element_by_xpath('//div[@foo="yay"]')
+          driver.find_element('xpath', '//div[@foo="yay"]')
           open('/tmp/passed', 'w')
         '';
       in [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
diff --git a/nixos/tests/nginx-globalredirect.nix b/nixos/tests/nginx-globalredirect.nix
new file mode 100644
index 000000000000..5f5f4f344d82
--- /dev/null
+++ b/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/nixos/tests/nginx-http3.nix b/nixos/tests/nginx-http3.nix
new file mode 100644
index 000000000000..319f6aac184a
--- /dev/null
+++ b/nixos/tests/nginx-http3.nix
@@ -0,0 +1,93 @@
+import ./make-test-python.nix ({lib, pkgs, ...}:
+let
+  hosts = ''
+    192.168.2.101 acme.test
+  '';
+
+in
+{
+  name = "nginx-http3";
+  meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.101"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 443 ];
+        firewall.allowedUDPPorts = [ 443 ];
+      };
+
+      security.pki.certificates = [
+        (builtins.readFile ./common/acme/server/ca.cert.pem)
+      ];
+
+      services.nginx = {
+        enable = true;
+        package = pkgs.nginxQuic;
+
+        virtualHosts."acme.test" = {
+          onlySSL = true;
+          sslCertificate = ./common/acme/server/acme.test.cert.pem;
+          sslCertificateKey = ./common/acme/server/acme.test.key.pem;
+          http2 = true;
+          http3 = true;
+          reuseport = true;
+          root = lib.mkForce (pkgs.runCommandLocal "testdir2" {} ''
+            mkdir "$out"
+            cat > "$out/index.html" <<EOF
+            <html><body>Hello World!</body></html>
+            EOF
+            cat > "$out/example.txt" <<EOF
+            Check http3 protocol.
+            EOF
+          '');
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.curlHTTP3 ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.201"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+      };
+
+      security.pki.certificates = [
+        (builtins.readFile ./common/acme/server/ca.cert.pem)
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("nginx")
+    server.wait_for_open_port(443)
+
+    # Check http connections
+    client.succeed("curl --verbose --http3 https://acme.test | grep 'Hello World!'")
+
+    # Check downloadings
+    client.succeed("curl --verbose --http3 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'")
+
+    # Check change User-Agent
+    client.succeed("curl --verbose --http3 --user-agent 'Curl test 3.0' https://acme.test")
+    server.succeed("cat /var/log/nginx/access.log | grep 'Curl test 3.0'")
+
+    server.shutdown()
+    client.shutdown()
+  '';
+})
diff --git a/nixos/tests/nginx-modsecurity.nix b/nixos/tests/nginx-modsecurity.nix
index 5ceee3787297..3c41da3e8d9b 100644
--- a/nixos/tests/nginx-modsecurity.nix
+++ b/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/nixos/tests/nginx-njs.nix b/nixos/tests/nginx-njs.nix
new file mode 100644
index 000000000000..72be16384f1b
--- /dev/null
+++ b/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/nixos/tests/nginx.nix b/nixos/tests/nginx.nix
index d9d073822a14..73f1133bd6ca 100644
--- a/nixos/tests/nginx.nix
+++ b/nixos/tests/nginx.nix
@@ -61,7 +61,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
       specialisation.reloadWithErrorsSystem.configuration = {
         services.nginx.package = pkgs.nginxMainline;
-        services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
+        services.nginx.virtualHosts."hello".extraConfig = "access_log /does/not/exist.log;";
       };
     };
   };
diff --git a/nixos/tests/nitter.nix b/nixos/tests/nitter.nix
index 0e1a6d150f38..8bc55ba8c69f 100644
--- a/nixos/tests/nitter.nix
+++ b/nixos/tests/nitter.nix
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
   testScript = ''
     machine.wait_for_unit("nitter.service")
-    machine.wait_for_open_port("80")
+    machine.wait_for_open_port(80)
     machine.succeed("curl --fail http://localhost:80/")
   '';
 })
diff --git a/nixos/tests/nix-ld.nix b/nixos/tests/nix-ld.nix
index 5c886182d969..8733f5b0c397 100644
--- a/nixos/tests/nix-ld.nix
+++ b/nixos/tests/nix-ld.nix
@@ -6,15 +6,12 @@ import ./make-test-python.nix ({ lib, pkgs, ...} :
     environment.systemPackages = [
       (pkgs.runCommand "patched-hello" {} ''
         install -D -m755 ${pkgs.hello}/bin/hello $out/bin/hello
-        patchelf $out/bin/hello --set-interpreter ${pkgs.nix-ld.ldPath}
+        patchelf $out/bin/hello --set-interpreter $(cat ${pkgs.nix-ld}/nix-support/ldpath)
       '')
     ];
   };
   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/nixos/tests/nixops/default.nix b/nixos/tests/nixops/default.nix
index 227b38815073..b77ac2476398 100644
--- a/nixos/tests/nixops/default.nix
+++ b/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 ];
diff --git a/nixos/tests/node-red.nix b/nixos/tests/node-red.nix
index 7660bc32f4c9..5f5960d68295 100644
--- a/nixos/tests/node-red.nix
+++ b/nixos/tests/node-red.nix
@@ -19,7 +19,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   testScript = ''
     start_all()
     nodered.wait_for_unit("node-red.service")
-    nodered.wait_for_open_port("1880")
+    nodered.wait_for_open_port(1880)
 
     client.wait_for_unit("multi-user.target")
 
diff --git a/nixos/tests/non-default-filesystems.nix b/nixos/tests/non-default-filesystems.nix
new file mode 100644
index 000000000000..7fa75aaad724
--- /dev/null
+++ b/nixos/tests/non-default-filesystems.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "non-default-filesystems";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    let
+      disk = config.virtualisation.bootDevice;
+    in
+    {
+      virtualisation.useDefaultFilesystems = false;
+
+      boot.initrd.availableKernelModules = [ "btrfs" ];
+      boot.supportedFilesystems = [ "btrfs" ];
+
+      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}
+
+          mkdir /nixos
+          mount -t btrfs ${disk} /nixos
+
+          ${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" ];
+        };
+
+        "/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")
+  '';
+})
diff --git a/nixos/tests/nscd.nix b/nixos/tests/nscd.nix
new file mode 100644
index 000000000000..1922812ef8c8
--- /dev/null
+++ b/nixos/tests/nscd.nix
@@ -0,0 +1,141 @@
+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 = {
+      withUnscd.configuration = { ... }: {
+        services.nscd.package = pkgs.unscd;
+      };
+      withNsncd.configuration = { ... }: {
+        services.nscd.enableNsncd = true;
+      };
+    };
+  };
+
+  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("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()
+
+      with subtest("nsncd"):
+          machine.succeed('${specialisations}/withNsncd/bin/switch-to-configuration test')
+          machine.wait_for_unit("default.target")
+
+          test_dynamic_user()
+          test_host_lookups()
+          test_nss_myhostname()
+    '';
+})
diff --git a/nixos/tests/ntfy-sh.nix b/nixos/tests/ntfy-sh.nix
new file mode 100644
index 000000000000..9a36fcdf3922
--- /dev/null
+++ b/nixos/tests/ntfy-sh.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix {
+  name = "ntfy-sh";
+
+  nodes.machine = { ... }: {
+    services.ntfy-sh.enable = true;
+  };
+
+  testScript = ''
+    import json
+
+    msg = "Test notification"
+
+    machine.wait_for_unit("multi-user.target")
+
+    machine.succeed(f"curl -d '{msg}' localhost:80/test")
+
+    notif = json.loads(machine.succeed("curl -s localhost:80/test/json?poll=1"))
+
+    assert msg == notif["message"], "Wrong message"
+  '';
+}
diff --git a/nixos/tests/oci-containers.nix b/nixos/tests/oci-containers.nix
index 68077e3540a5..1bcfb276dbee 100644
--- a/nixos/tests/oci-containers.nix
+++ b/nixos/tests/oci-containers.nix
@@ -12,7 +12,7 @@ let
     name = "oci-containers-${backend}";
 
     meta = {
-      maintainers = with lib.maintainers; [ adisbladis benley ] ++ lib.teams.serokell.members;
+      maintainers = with lib.maintainers; [ adisbladis benley mkaito ] ++ lib.teams.serokell.members;
     };
 
     nodes = {
diff --git a/nixos/tests/ombi.nix b/nixos/tests/ombi.nix
index bfca86af8175..ce3064ce6ac6 100644
--- a/nixos/tests/ombi.nix
+++ b/nixos/tests/ombi.nix
@@ -12,7 +12,7 @@ with lib;
 
   testScript = ''
     machine.wait_for_unit("ombi.service")
-    machine.wait_for_open_port("5000")
+    machine.wait_for_open_port(5000)
     machine.succeed("curl --fail http://localhost:5000/")
   '';
 })
diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix
index 3c388119d5d2..075bb5d1f640 100644
--- a/nixos/tests/openldap.nix
+++ b/nixos/tests/openldap.nix
@@ -1,9 +1,4 @@
-{ pkgs ? (import ../.. { inherit system; config = { }; })
-, system ? builtins.currentSystem
-, ...
-}:
-
-let
+import ./make-test-python.nix ({ pkgs, ... }: let
   dbContents = ''
     dn: dc=example
     objectClass: domain
@@ -13,118 +8,149 @@ let
     objectClass: organizationalUnit
     ou: users
   '';
-  testScript = ''
-    machine.wait_for_unit("openldap.service")
-    machine.succeed(
-        'ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"',
-    )
+
+  ldifConfig = ''
+    dn: cn=config
+    cn: config
+    objectClass: olcGlobal
+    olcLogLevel: stats
+
+    dn: cn=schema,cn=config
+    cn: schema
+    objectClass: olcSchemaConfig
+
+    include: file://${pkgs.openldap}/etc/schema/core.ldif
+    include: file://${pkgs.openldap}/etc/schema/cosine.ldif
+    include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+
+    dn: olcDatabase={0}config,cn=config
+    olcDatabase: {0}config
+    objectClass: olcDatabaseConfig
+    olcRootDN: cn=root,cn=config
+    olcRootPW: configpassword
+
+    dn: olcDatabase={1}mdb,cn=config
+    objectClass: olcDatabaseConfig
+    objectClass: olcMdbConfig
+    olcDatabase: {1}mdb
+    olcDbDirectory: /var/db/openldap
+    olcDbIndex: objectClass eq
+    olcSuffix: dc=example
+    olcRootDN: cn=root,dc=example
+    olcRootPW: notapassword
   '';
+
+  ldapClientConfig = {
+    enable = true;
+    loginPam = false;
+    nsswitch = false;
+    server = "ldap://";
+    base = "dc=example";
+  };
+
 in {
-  # New-style configuration
-  current = import ./make-test-python.nix ({ pkgs, ... }: {
-    inherit testScript;
-    name = "openldap";
+  name = "openldap";
+
+  nodes.machine = { pkgs, ... }: {
+    environment.etc."openldap/root_password".text = "notapassword";
 
-    nodes.machine = { pkgs, ... }: {
-      environment.etc."openldap/root_password".text = "notapassword";
-      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" = {
-              # This tests string, base64 and path values, as well as lists of string values
-              attrs = {
-                objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
-                olcDatabase = "{1}mdb";
-                olcDbDirectory = "/var/db/openldap";
-                olcSuffix = "dc=example";
-                olcRootDN = {
-                  # cn=root,dc=example
-                  base64 = "Y249cm9vdCxkYz1leGFtcGxl";
-                };
-                olcRootPW = {
-                  path = "/etc/openldap/root_password";
-                };
+    users.ldap = ldapClientConfig;
+
+    services.openldap = {
+      enable = true;
+      urlList = [ "ldapi:///" "ldap://" ];
+      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={0}config" = {
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" ];
+              olcDatabase = "{0}config";
+              olcRootDN = "cn=root,cn=config";
+              olcRootPW = "configpassword";
+            };
+          };
+          "olcDatabase={1}mdb" = {
+            # This tests string, base64 and path values, as well as lists of string values
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+              olcDatabase = "{1}mdb";
+              olcDbDirectory = "/var/lib/openldap/db";
+              olcSuffix = "dc=example";
+              olcRootDN = {
+                # cn=root,dc=example
+                base64 = "Y249cm9vdCxkYz1leGFtcGxl";
+              };
+              olcRootPW = {
+                path = "/etc/openldap/root_password";
               };
             };
           };
         };
-        declarativeContents."dc=example" = dbContents;
       };
     };
-  }) { inherit pkgs system; };
-
-  # Old-style configuration
-  oldOptions = import ./make-test-python.nix ({ pkgs, ... }: {
-    inherit testScript;
-    name = "openldap";
 
-    nodes.machine = { pkgs, ... }: {
-      services.openldap = {
-        enable = true;
-        logLevel = "stats acl";
-        defaultSchemas = true;
-        database = "mdb";
-        suffix = "dc=example";
-        rootdn = "cn=root,dc=example";
-        rootpw = "notapassword";
-        declarativeContents."dc=example" = dbContents;
+    specialisation = {
+      declarativeContents.configuration = { ... }: {
+        services.openldap.declarativeContents."dc=example" = dbContents;
       };
-    };
-  }) { inherit system pkgs; };
-
-  # Manually managed configDir, for example if dynamic config is essential
-  manualConfigDir = import ./make-test-python.nix ({ pkgs, ... }: {
-    name = "openldap";
-
-    nodes.machine = { pkgs, ... }: {
-      services.openldap = {
-        enable = true;
-        configDir = "/var/db/slapd.d";
+      mutableConfig.configuration = { ... }: {
+        services.openldap = {
+          declarativeContents."dc=example" = dbContents;
+          mutableConfig = true;
+        };
+      };
+      manualConfigDir = {
+        inheritParentConfig = false;
+        configuration = { ... }: {
+          users.ldap = ldapClientConfig;
+          services.openldap = {
+            enable = true;
+            configDir = "/var/db/slapd.d";
+          };
+        };
       };
     };
+  };
+  testScript = { nodes, ... }: let
+    specializations = "${nodes.machine.config.system.build.toplevel}/specialisation";
+    changeRootPw = ''
+      dn: olcDatabase={1}mdb,cn=config
+      changetype: modify
+      replace: olcRootPW
+      olcRootPW: foobar
+    '';
+  in ''
+    # Test startup with empty DB
+    machine.wait_for_unit("openldap.service")
 
-    testScript = let
-      contents = pkgs.writeText "data.ldif" dbContents;
-      config = pkgs.writeText "config.ldif" ''
-        dn: cn=config
-        cn: config
-        objectClass: olcGlobal
-        olcLogLevel: stats
-        olcPidFile: /run/slapd/slapd.pid
-
-        dn: cn=schema,cn=config
-        cn: schema
-        objectClass: olcSchemaConfig
+    with subtest("declarative contents"):
+      machine.succeed('${specializations}/declarativeContents/bin/switch-to-configuration test')
+      machine.wait_for_unit("openldap.service")
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword')
+      machine.fail('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
 
-        include: file://${pkgs.openldap}/etc/schema/core.ldif
-        include: file://${pkgs.openldap}/etc/schema/cosine.ldif
-        include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+    with subtest("mutable config"):
+      machine.succeed('${specializations}/mutableConfig/bin/switch-to-configuration test')
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword')
+      machine.succeed('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar')
 
-        dn: olcDatabase={1}mdb,cn=config
-        objectClass: olcDatabaseConfig
-        objectClass: olcMdbConfig
-        olcDatabase: {1}mdb
-        olcDbDirectory: /var/db/openldap
-        olcDbIndex: objectClass eq
-        olcSuffix: dc=example
-        olcRootDN: cn=root,dc=example
-        olcRootPW: notapassword
-      '';
-    in ''
+    with subtest("manual config dir"):
       machine.succeed(
-          "mkdir -p /var/db/slapd.d /var/db/openldap",
-          "slapadd -F /var/db/slapd.d -n0 -l ${config}",
-          "slapadd -F /var/db/slapd.d -n1 -l ${contents}",
-          "chown -R openldap:openldap /var/db/slapd.d /var/db/openldap",
-          "systemctl restart openldap",
+        'mkdir /var/db/slapd.d /var/db/openldap',
+        'slapadd -F /var/db/slapd.d -n0 -l ${pkgs.writeText "config.ldif" ldifConfig}',
+        'slapadd -F /var/db/slapd.d -n1 -l ${pkgs.writeText "contents.ldif" dbContents}',
+        'chown -R openldap:openldap /var/db/slapd.d /var/db/openldap',
+        '${specializations}/manualConfigDir/bin/switch-to-configuration test',
       )
-    '' + testScript;
-  }) { inherit system pkgs; };
-}
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword')
+      machine.succeed('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar')
+  '';
+})
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
index 003813379e69..4083f5906d79 100644
--- a/nixos/tests/openssh.nix
+++ b/nixos/tests/openssh.nix
@@ -80,17 +80,21 @@ in {
 
         client.wait_for_unit("network.target")
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2",
+            timeout=30
         )
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024",
+            timeout=30
         )
 
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'echo hello world' >&2",
+            timeout=30
         )
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server_lazy 'ulimit -l' | grep 1024",
+            timeout=30
         )
 
     with subtest("configured-authkey"):
@@ -99,10 +103,12 @@ in {
         )
         client.succeed("chmod 600 privkey.snakeoil")
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true",
+            timeout=30
         )
         client.succeed(
-            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server_lazy true"
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server_lazy true",
+            timeout=30
         )
 
     with subtest("localhost-only"):
diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix
index ac05bd80c601..8f3e2494047c 100644
--- a/nixos/tests/os-prober.nix
+++ b/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; ''
@@ -75,21 +75,30 @@ in {
       # The test cannot access the network, so any packages
       # nixos-rebuild needs must be included in the VM.
       system.extraDependencies = with pkgs;
-        [ sudo
-          libxml2.bin
-          libxslt.bin
+        [
+          brotli
+          brotli.dev
+          brotli.lib
           desktop-file-utils
           docbook5
           docbook_xsl_ns
-          unionfs-fuse
-          ntp
+          grub2
+          kmod.dev
+          libarchive
+          libarchive.dev
+          libxml2.bin
+          libxslt.bin
           nixos-artwork.wallpapers.simple-dark-gray-bottom
-          perlPackages.XMLLibXML
+          ntp
           perlPackages.ListCompare
+          perlPackages.XMLLibXML
+          python3Minimal
           shared-mime-info
+          stdenv
+          sudo
           texinfo
+          unionfs-fuse
           xorg.lndir
-          grub2
 
           # add curl so that rather than seeing the test attempt to download
           # curl's tarball, we see what it's trying to download
diff --git a/nixos/tests/pam/pam-file-contents.nix b/nixos/tests/pam/pam-file-contents.nix
index 86c61003aeb6..2bafd90618e9 100644
--- a/nixos/tests/pam/pam-file-contents.nix
+++ b/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/nixos/tests/pam/pam-oath-login.nix b/nixos/tests/pam/pam-oath-login.nix
index 8fb7553de907..dd6ef4a0abcb 100644
--- a/nixos/tests/pam/pam-oath-login.nix
+++ b/nixos/tests/pam/pam-oath-login.nix
@@ -7,7 +7,7 @@ let
   # how many passwords have been made. In this env, we'll always be on
   # the 0th counter, so the password is static.
   #
-  # Generated in nix-shell -p oathToolkit
+  # Generated in nix-shell -p oath-toolkit
   # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
   # and picking a the first 4:
   oathSnakeOilPassword1 = "143349";
@@ -77,28 +77,28 @@ in
     machine.screenshot("postboot")
 
     with subtest("Invalid password"):
-        switch_to_tty(2)
-        enter_user_alice(2)
+        switch_to_tty("2")
+        enter_user_alice("2")
 
         machine.send_chars("${oathSnakeOilPassword1}\n")
-        machine.wait_until_tty_matches(2, "Password: ")
+        machine.wait_until_tty_matches("2", "Password: ")
         machine.send_chars("blorg\n")
-        machine.wait_until_tty_matches(2, "Login incorrect")
+        machine.wait_until_tty_matches("2", "Login incorrect")
 
     with subtest("Invalid oath token"):
-        switch_to_tty(3)
-        enter_user_alice(3)
+        switch_to_tty("3")
+        enter_user_alice("3")
 
         machine.send_chars("000000\n")
-        machine.wait_until_tty_matches(3, "Login incorrect")
-        machine.wait_until_tty_matches(3, "login:")
+        machine.wait_until_tty_matches("3", "Login incorrect")
+        machine.wait_until_tty_matches("3", "login:")
 
     with subtest("Happy path: Both passwords are mandatory to get us in"):
-        switch_to_tty(4)
-        enter_user_alice(4)
+        switch_to_tty("4")
+        enter_user_alice("4")
 
         machine.send_chars("${oathSnakeOilPassword2}\n")
-        machine.wait_until_tty_matches(4, "Password: ")
+        machine.wait_until_tty_matches("4", "Password: ")
         machine.send_chars("${alicePassword}\n")
 
         machine.wait_until_succeeds("pgrep -u alice bash")
diff --git a/nixos/tests/pam/pam-u2f.nix b/nixos/tests/pam/pam-u2f.nix
index d7c540982cfa..07408dea797e 100644
--- a/nixos/tests/pam/pam-u2f.nix
+++ b/nixos/tests/pam/pam-u2f.nix
@@ -12,6 +12,7 @@ import ../make-test-python.nix ({ ... }:
         debug = true;
         enable = true;
         interactive = true;
+        origin = "nixos-test";
       };
     };
 
@@ -19,7 +20,7 @@ import ../make-test-python.nix ({ ... }:
     ''
       machine.wait_for_unit("multi-user.target")
       machine.succeed(
-          'egrep "auth required .*/lib/security/pam_u2f.so.*debug.*interactive.*cue" /etc/pam.d/ -R'
+          'egrep "auth required .*/lib/security/pam_u2f.so.*debug.*interactive.*cue.*origin=nixos-test" /etc/pam.d/ -R'
       )
     '';
 })
diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix
index 51fe7c207851..b97834835c2c 100644
--- a/nixos/tests/paperless.nix
+++ b/nixos/tests/paperless.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ lib, ... }: {
   name = "paperless";
-  meta.maintainers = with lib.maintainers; [ earvstedt Flakebi ];
+  meta.maintainers = with lib.maintainers; [ erikarvstedt Flakebi ];
 
   nodes.machine = { pkgs, ... }: {
     environment.systemPackages = with pkgs; [ imagemagick jq ];
@@ -40,5 +40,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/nixos/tests/pass-secret-service.nix b/nixos/tests/pass-secret-service.nix
new file mode 100644
index 000000000000..a85a508bfe16
--- /dev/null
+++ b/nixos/tests/pass-secret-service.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "pass-secret-service";
+  meta.maintainers = with lib; [ aidalgol ];
+
+  nodes.machine = { nodes, pkgs, ... }:
+    {
+      imports = [ ./common/user-account.nix ];
+
+      services.passSecretService.enable = true;
+
+      environment.systemPackages = [
+        # Create a script that tries to make a request to the D-Bus secrets API.
+        (pkgs.writers.writePython3Bin "secrets-dbus-init"
+          {
+            libraries = [ pkgs.python3Packages.secretstorage ];
+          } ''
+          import secretstorage
+          print("Initializing dbus connection...")
+          connection = secretstorage.dbus_init()
+          print("Requesting default collection...")
+          collection = secretstorage.get_default_collection(connection)
+          print("Done!  dbus-org.freedesktop.secrets should now be active.")
+        '')
+        pkgs.pass
+      ];
+
+      programs.gnupg = {
+        agent.enable = true;
+        agent.pinentryFlavor = "tty";
+        dirmngr.enable = true;
+      };
+    };
+
+  # Some of the commands are run via a virtual console because they need to be
+  # run under a real login session, with D-Bus running in the environment.
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.config.users.users.alice;
+      gpg-uid = "alice@example.net";
+      gpg-pw = "foobar9000";
+      ready-file = "/tmp/secrets-dbus-init.done";
+    in
+    ''
+      # Initialise the pass(1) storage.
+      machine.succeed("""
+        sudo -u alice gpg --pinentry-mode loopback --batch --passphrase ${gpg-pw} \
+        --quick-gen-key ${gpg-uid} \
+      """)
+      machine.succeed("sudo -u alice pass init ${gpg-uid}")
+
+      with subtest("Service is not running on login"):
+          machine.wait_until_tty_matches("1", "login: ")
+          machine.send_chars("alice\n")
+          machine.wait_until_tty_matches("1", "login: alice")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("1", "Password: ")
+          machine.send_chars("${user.password}\n")
+          machine.wait_until_succeeds("pgrep -u alice bash")
+
+          _, output = machine.systemctl("status dbus-org.freedesktop.secrets --no-pager", "alice")
+          assert "Active: inactive (dead)" in output
+
+      with subtest("Service starts after a client tries to talk to the D-Bus API"):
+          machine.send_chars("secrets-dbus-init; touch ${ready-file}\n")
+          machine.wait_for_file("${ready-file}")
+          _, output = machine.systemctl("status dbus-org.freedesktop.secrets --no-pager", "alice")
+          assert "Active: active (running)" in output
+    '';
+})
diff --git a/nixos/tests/patroni.nix b/nixos/tests/patroni.nix
new file mode 100644
index 000000000000..1f15cd59677a
--- /dev/null
+++ b/nixos/tests/patroni.nix
@@ -0,0 +1,206 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+  let
+    nodesIps = [
+      "192.168.1.1"
+      "192.168.1.2"
+      "192.168.1.3"
+    ];
+
+    createNode = index: { pkgs, ... }:
+      let
+        ip = builtins.elemAt nodesIps index; # since we already use IPs to identify servers
+      in
+      {
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = ip; prefixLength = 16; }
+        ];
+
+        networking.firewall.allowedTCPPorts = [ 5432 8008 5010 ];
+
+        environment.systemPackages = [ pkgs.jq ];
+
+        services.patroni = {
+
+          enable = true;
+
+          postgresqlPackage = pkgs.postgresql_14.withPackages (p: [ p.pg_safeupdate ]);
+
+          scope = "cluster1";
+          name = "node${toString(index + 1)}";
+          nodeIp = ip;
+          otherNodesIps = builtins.filter (h: h != ip) nodesIps;
+          softwareWatchdog = true;
+
+          settings = {
+            bootstrap = {
+              dcs = {
+                ttl = 30;
+                loop_wait = 10;
+                retry_timeout = 10;
+                maximum_lag_on_failover = 1048576;
+              };
+              initdb = [
+                { encoding = "UTF8"; }
+                "data-checksums"
+              ];
+            };
+
+            postgresql = {
+              use_pg_rewind = true;
+              use_slots = true;
+              authentication = {
+                replication = {
+                  username = "replicator";
+                };
+                superuser = {
+                  username = "postgres";
+                };
+                rewind = {
+                  username = "rewind";
+                };
+              };
+              parameters = {
+                listen_addresses = "${ip}";
+                wal_level = "replica";
+                hot_standby_feedback = "on";
+                unix_socket_directories = "/tmp";
+              };
+              pg_hba = [
+                "host replication replicator 192.168.1.0/24 md5"
+                # Unsafe, do not use for anything other than tests
+                "host all all 0.0.0.0/0 trust"
+              ];
+            };
+
+            etcd3 = {
+              host = "192.168.1.4:2379";
+            };
+          };
+
+          environmentFiles = {
+            PATRONI_REPLICATION_PASSWORD = pkgs.writeText "replication-password" "postgres";
+            PATRONI_SUPERUSER_PASSWORD = pkgs.writeText "superuser-password" "postgres";
+            PATRONI_REWIND_PASSWORD = pkgs.writeText "rewind-password" "postgres";
+          };
+        };
+
+        # We always want to restart so the tests never hang
+        systemd.services.patroni.serviceConfig.StartLimitIntervalSec = 0;
+      };
+  in
+  {
+    name = "patroni";
+
+    nodes = {
+      node1 = createNode 0;
+      node2 = createNode 1;
+      node3 = createNode 2;
+
+      etcd = { pkgs, ... }: {
+
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.4"; prefixLength = 16; }
+        ];
+
+        services.etcd = {
+          enable = true;
+          listenClientUrls = [ "http://192.168.1.4:2379" ];
+        };
+
+        networking.firewall.allowedTCPPorts = [ 2379 ];
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [ pkgs.postgresql_14 ];
+
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.2.1"; prefixLength = 16; }
+        ];
+
+        services.haproxy = {
+          enable = true;
+          config = ''
+            global
+                maxconn 100
+
+            defaults
+                log global
+                mode tcp
+                retries 2
+                timeout client 30m
+                timeout connect 4s
+                timeout server 30m
+                timeout check 5s
+
+            listen cluster1
+                bind 127.0.0.1:5432
+                option httpchk
+                http-check expect status 200
+                default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
+                ${builtins.concatStringsSep "\n" (map (ip: "server postgresql_${ip}_5432 ${ip}:5432 maxconn 100 check port 8008") nodesIps)}
+          '';
+        };
+      };
+    };
+
+
+
+    testScript = ''
+      nodes = [node1, node2, node3]
+
+      def wait_for_all_nodes_ready(expected_replicas=2):
+          booted_nodes = filter(lambda node: node.booted, nodes)
+          for node in booted_nodes:
+              print(node.succeed("patronictl list cluster1"))
+              node.wait_until_succeeds(f"[ $(patronictl list -f json cluster1 | jq 'length') == {expected_replicas + 1} ]")
+              node.wait_until_succeeds("[ $(patronictl list -f json cluster1 | jq 'map(select(.Role | test(\"^Leader$\"))) | map(select(.State | test(\"^running$\"))) | length') == 1 ]")
+              node.wait_until_succeeds(f"[ $(patronictl list -f json cluster1 | jq 'map(select(.Role | test(\"^Replica$\"))) | map(select(.State | test(\"^running$\"))) | length') == {expected_replicas} ]")
+              print(node.succeed("patronictl list cluster1"))
+          client.wait_until_succeeds("psql -h 127.0.0.1 -U postgres --command='select 1;'")
+
+      def run_dummy_queries():
+          client.succeed("psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='insert into dummy(val) values (101);'")
+          client.succeed("test $(psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='select val from dummy where val = 101;') -eq 101")
+          client.succeed("psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='delete from dummy where val = 101;'")
+
+      start_all()
+
+      etcd.wait_for_unit("etcd.service")
+
+      with subtest("should bootstrap a new patroni cluster"):
+          wait_for_all_nodes_ready()
+
+      with subtest("should be able to insert and select"):
+          client.succeed("psql -h 127.0.0.1 -U postgres --command='create table dummy as select * from generate_series(1, 100) as val;'")
+          client.succeed("test $(psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100")
+
+      with subtest("should restart after all nodes are crashed"):
+          for node in nodes:
+              node.crash()
+          for node in nodes:
+              node.start()
+          wait_for_all_nodes_ready()
+
+      with subtest("should be able to run queries while any one node is crashed"):
+          masterNodeName = node1.succeed("patronictl list -f json cluster1 | jq '.[] | select(.Role | test(\"^Leader$\")) | .Member' -r").strip()
+          masterNodeIndex = int(masterNodeName[len(masterNodeName)-1]) - 1
+
+          # Move master node at the end of the list to avoid multiple failovers (makes the test faster and more consistent)
+          nodes.append(nodes.pop(masterNodeIndex))
+
+          for node in nodes:
+              node.crash()
+              wait_for_all_nodes_ready(1)
+
+              # Execute some queries while a node is down.
+              run_dummy_queries()
+
+              # Restart crashed node.
+              node.start()
+              wait_for_all_nodes_ready()
+
+              # Execute some queries with the node back up.
+              run_dummy_queries()
+    '';
+  })
diff --git a/nixos/tests/pdns-recursor.nix b/nixos/tests/pdns-recursor.nix
index cf473a064313..14f1b7ea8a35 100644
--- a/nixos/tests/pdns-recursor.nix
+++ b/nixos/tests/pdns-recursor.nix
@@ -9,7 +9,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   testScript = ''
     server.wait_for_unit("pdns-recursor")
-    server.wait_for_open_port("53")
+    server.wait_for_open_port(53)
     assert "192.0.2.1" in server.succeed("host example.com localhost")
   '';
 })
diff --git a/nixos/tests/pgadmin4-standalone.nix b/nixos/tests/pgadmin4-standalone.nix
index 442570c5306b..5aa17fcb5bb9 100644
--- a/nixos/tests/pgadmin4-standalone.nix
+++ b/nixos/tests/pgadmin4-standalone.nix
@@ -1,5 +1,5 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }:
-  # This is seperate from pgadmin4 since we don't want both running at once
+  # This is separate from pgadmin4 since we don't want both running at once
 
   {
     name = "pgadmin4-standalone";
diff --git a/nixos/tests/pgadmin4.nix b/nixos/tests/pgadmin4.nix
index 2f6dc3bd569f..2a2b5aaa2841 100644
--- a/nixos/tests/pgadmin4.nix
+++ b/nixos/tests/pgadmin4.nix
@@ -1,53 +1,27 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }:
+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";
 
-    python-with-needed-packages = pkgs.python3.withPackages (ps: with ps; [
-      selenium
-      testtools
-      testscenarios
-      flask
-      flask-babelex
-      flask-babel
-      flask-gravatar
-      flask_login
-      flask_mail
-      flask_migrate
-      flask_sqlalchemy
-      flask_wtf
-      flask-compress
-      passlib
-      pytz
-      simplejson
-      six
-      sqlparse
-      wtforms
-      flask-paranoid
-      psutil
-      psycopg2
-      python-dateutil
-      sqlalchemy
-      itsdangerous
-      flask-security-too
-      bcrypt
-      cryptography
-      sshtunnel
-      ldap3
-      gssapi
-      flask-socketio
-      eventlet
-      httpagentparser
-      user-agents
-      wheel
-      authlib
-      qrcode
-      pillow
-      pyotp
-      boto3
-    ]);
   in
   {
     name = "pgadmin4";
@@ -55,12 +29,27 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
     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;
+        })
+      ];
+
       environment.systemPackages = with pkgs; [
         pgadmin4
         postgresql
-        python-with-needed-packages
         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;
@@ -117,12 +106,13 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
            && sed -i 's|driver_local.maximize_window()||' web/regression/runtests.py"
       )
 
-      # don't bother to test LDAP authentification
+      # Don't bother to test LDAP or kerberos authentication
       with subtest("run browser test"):
           machine.succeed(
                'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
-               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg browser --exclude \
-               browser.tests.test_ldap_login.LDAPLoginTestCase,browser.tests.test_ldap_login'
+               && python regression/runtests.py \
+               --pkg browser \
+               --exclude browser.tests.test_ldap_login.LDAPLoginTestCase,browser.tests.test_ldap_login,browser.tests.test_kerberos_with_mocking'
           )
 
       # fontconfig is necessary for chromium to run
@@ -131,13 +121,13 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
           machine.succeed(
               'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
                && export FONTCONFIG_FILE=${pkgs.makeFontsConf { fontDirectories = [];}} \
-               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg feature_tests'
+               && python regression/runtests.py --pkg feature_tests'
           )
 
       with subtest("run resql test"):
-          machine.succeed(
-               'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
-               && ${python-with-needed-packages.interpreter} regression/runtests.py --pkg resql'
-          )
+         machine.succeed(
+              'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \
+              && python regression/runtests.py --pkg resql'
+         )
     '';
   })
diff --git a/nixos/tests/phosh.nix b/nixos/tests/phosh.nix
new file mode 100644
index 000000000000..25bf4848542e
--- /dev/null
+++ b/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 = [ 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/nixos/tests/php/pcre.nix b/nixos/tests/php/pcre.nix
index 57407477f4b8..8e37d5dcf97b 100644
--- a/nixos/tests/php/pcre.nix
+++ b/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/nixos/tests/pict-rs.nix b/nixos/tests/pict-rs.nix
index 90f01d6d5d02..4315e9fb6e90 100644
--- a/nixos/tests/pict-rs.nix
+++ b/nixos/tests/pict-rs.nix
@@ -12,6 +12,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       start_all()
 
       machine.wait_for_unit("pict-rs")
-      machine.wait_for_open_port("8080")
+      machine.wait_for_open_port(8080)
     '';
   })
diff --git a/nixos/tests/pinnwand.nix b/nixos/tests/pinnwand.nix
index 0391c4133111..42b26e08c189 100644
--- a/nixos/tests/pinnwand.nix
+++ b/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/nixos/tests/plasma-bigscreen.nix b/nixos/tests/plasma-bigscreen.nix
new file mode 100644
index 000000000000..1c61cafcbff3
--- /dev/null
+++ b/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/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index 3358a72570e8..b3836cf641d4 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -13,7 +13,10 @@ import ./make-test-python.nix ({ pkgs, ...} :
     services.xserver.enable = true;
     services.xserver.displayManager.sddm.enable = true;
     services.xserver.displayManager.defaultSession = "plasma";
-    services.xserver.desktopManager.plasma5.enable = true;
+    services.xserver.desktopManager.plasma5 = {
+      enable = true;
+      excludePackages = [ pkgs.plasma5Packages.elisa ];
+    };
     services.xserver.displayManager.autoLogin = {
       enable = true;
       user = "alice";
@@ -40,6 +43,9 @@ import ./make-test-python.nix ({ pkgs, ...} :
     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("Ensure Elisa is not installed"):
+        machine.fail("which elisa")
+
     with subtest("Run Dolphin"):
         machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin >&2 &'")
         machine.wait_for_window(" Dolphin")
diff --git a/nixos/tests/please.nix b/nixos/tests/please.nix
new file mode 100644
index 000000000000..2437cfe16130
--- /dev/null
+++ b/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 = with lib; mkMerge [
+        (listToAttrs (map
+          (n: nameValuePair n { isNormalUser = true; })
+          (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/nixos/tests/pleroma.nix b/nixos/tests/pleroma.nix
index 90a9a2511044..8998716243a2 100644
--- a/nixos/tests/pleroma.nix
+++ b/nixos/tests/pleroma.nix
@@ -158,7 +158,9 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
     # Waiting for pleroma to be up.
     timeout 5m bash -c 'while [[ "$(curl -s -o /dev/null -w '%{http_code}' https://pleroma.nixos.test/api/v1/instance)" != "200" ]]; do sleep 2; done'
-    pleroma_ctl user new jamy jamy@nixos.test --password 'jamy-password' --moderator --admin -y
+    # Toremove the RELEASE_COOKIE bit when https://github.com/NixOS/nixpkgs/issues/166229 gets fixed.
+    RELEASE_COOKIE="/var/lib/pleroma/.cookie" \
+      pleroma_ctl user new jamy jamy@nixos.test --password 'jamy-password' --moderator --admin -y
   '';
 
   tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
diff --git a/nixos/tests/plikd.nix b/nixos/tests/plikd.nix
index 643fd5bfcd37..97c254a5f7b0 100644
--- a/nixos/tests/plikd.nix
+++ b/nixos/tests/plikd.nix
@@ -15,7 +15,7 @@ import ./make-test-python.nix ({ lib, ... }: {
     machine.wait_for_unit("plikd")
 
     # Network test
-    machine.wait_for_open_port("8080")
+    machine.wait_for_open_port(8080)
     machine.succeed("curl --fail -v http://localhost:8080")
 
     # Application test
diff --git a/nixos/tests/podgrab.nix b/nixos/tests/podgrab.nix
index e927e25fea56..dc9dfebaf49b 100644
--- a/nixos/tests/podgrab.nix
+++ b/nixos/tests/podgrab.nix
@@ -22,11 +22,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     start_all()
 
     default.wait_for_unit("podgrab")
-    default.wait_for_open_port("${toString defaultPort}")
+    default.wait_for_open_port(${toString defaultPort})
     default.succeed("curl --fail http://localhost:${toString defaultPort}")
 
     customized.wait_for_unit("podgrab")
-    customized.wait_for_open_port("${toString customPort}")
+    customized.wait_for_open_port(${toString customPort})
     customized.succeed("curl --fail http://localhost:${toString customPort}")
   '';
 
diff --git a/nixos/tests/podman/default.nix b/nixos/tests/podman/default.nix
index 67c7823c5a31..106ba2057d06 100644
--- a/nixos/tests/podman/default.nix
+++ b/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,31 @@ 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";
-          };
+      podman = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        users.users.alice = {
+          isNormalUser = true;
+        };
+      };
+      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 = ''
@@ -45,6 +43,7 @@ import ../make-test-python.nix (
 
 
       podman.wait_for_unit("sockets.target")
+      docker.wait_for_unit("sockets.target")
       start_all()
 
       with subtest("Run container as root with runc"):
@@ -74,8 +73,10 @@ import ../make-test-python.nix (
           podman.succeed("podman stop sleeping")
           podman.succeed("podman rm sleeping")
 
-      # create systemd session for rootless
+      # start systemd session for rootless
       podman.succeed("loginctl enable-linger alice")
+      podman.succeed(su_cmd("whoami"))
+      podman.sleep(1)
 
       with subtest("Run container rootless with runc"):
           podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
@@ -120,22 +121,22 @@ import ../make-test-python.nix (
           assert pid == "2"
 
       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("docker network create default")
+          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")
+          docker.succeed("docker network rm default")
 
       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/nixos/tests/polaris.nix b/nixos/tests/polaris.nix
new file mode 100644
index 000000000000..fb2e67f075aa
--- /dev/null
+++ b/nixos/tests/polaris.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "polaris";
+  meta.maintainers = with maintainers; [ pbsds ];
+
+  nodes.machine =
+    { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      services.polaris = {
+        enable = true;
+        port = 5050;
+        settings.users = [
+          {
+            name = "test_user";
+            password = "very_secret_password";
+            admin = true;
+          }
+        ];
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("polaris.service")
+    machine.wait_for_open_port(5050)
+    machine.succeed("curl http://localhost:5050/api/version")
+    machine.succeed("curl -X GET http://localhost:5050/api/initial_setup -H  'accept: application/json' | jq -e '.has_any_users == true'")
+  '';
+})
diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix
index 7864f5d6ff32..7e0a82c38828 100644
--- a/nixos/tests/postgresql.nix
+++ b/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 ];
+    };
+
+    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/nixos/tests/powerdns.nix b/nixos/tests/powerdns.nix
index 70060bad87b6..d3708d25f0fb 100644
--- a/nixos/tests/powerdns.nix
+++ b/nixos/tests/powerdns.nix
@@ -47,7 +47,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     with subtest("Adding an example zone works"):
         # Extract configuration file needed by pdnsutil
         unit = server.succeed("systemctl cat pdns")
-        conf = re.search("(--config-dir=[^ ]+)", unit).group(1)
+        match = re.search("(--config-dir=[^ ]+)", unit)
+        assert(match is not None)
+        conf = match.group(1)
         pdnsutil = "sudo -u pdns pdnsutil " + conf
         server.succeed(f"{pdnsutil} create-zone example.com ns1.example.com")
         server.succeed(f"{pdnsutil} add-record  example.com ns1 A 192.168.1.2")
diff --git a/nixos/tests/pppd.nix b/nixos/tests/pppd.nix
index bda0aa75bb50..e714a6c21a6c 100644
--- a/nixos/tests/pppd.nix
+++ b/nixos/tests/pppd.nix
@@ -5,6 +5,8 @@ import ./make-test-python.nix (
       mode = "0640";
     };
   in {
+    name = "pppd";
+
     nodes = {
       server = {config, pkgs, ...}: {
         config = {
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
index 6338fd8d8ac1..cfebe232d92a 100644
--- a/nixos/tests/printing.nix
+++ b/nixos/tests/printing.nix
@@ -4,6 +4,7 @@ import ./make-test-python.nix ({pkgs, ... }:
 let
   printingServer = startWhenNeeded: {
     services.printing.enable = true;
+    services.printing.stateless = true;
     services.printing.startWhenNeeded = startWhenNeeded;
     services.printing.listenAddresses = [ "*:631" ];
     services.printing.defaultShared = true;
diff --git a/nixos/tests/privacyidea.nix b/nixos/tests/privacyidea.nix
index fb072514dd90..401ad72c37b7 100644
--- a/nixos/tests/privacyidea.nix
+++ b/nixos/tests/privacyidea.nix
@@ -3,7 +3,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : rec {
   name = "privacyidea";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ fpletz ];
+    maintainers = [ ];
   };
 
   nodes.machine = { ... }: {
diff --git a/nixos/tests/privoxy.nix b/nixos/tests/privoxy.nix
index 47072ce4b0af..2d95c4522a01 100644
--- a/nixos/tests/privoxy.nix
+++ b/nixos/tests/privoxy.nix
@@ -81,23 +81,23 @@ in
     ''
       with subtest("Privoxy is running"):
           machine.wait_for_unit("privoxy")
-          machine.wait_for_open_port("8118")
+          machine.wait_for_open_port(8118)
           machine.succeed("curl -f http://config.privoxy.org")
 
       with subtest("Privoxy can filter http requests"):
-          machine.wait_for_open_port("80")
+          machine.wait_for_open_port(80)
           assert "great day" in machine.succeed(
               "curl -sfL http://example.com/how-are-you? | tee /dev/stderr"
           )
 
       with subtest("Privoxy can filter https requests"):
-          machine.wait_for_open_port("443")
+          machine.wait_for_open_port(443)
           assert "great day" in machine.succeed(
               "curl -sfL https://example.com/how-are-you? | tee /dev/stderr"
           )
 
       with subtest("Blocks are working"):
-          machine.wait_for_open_port("443")
+          machine.wait_for_open_port(443)
           machine.fail("curl -f https://example.com/ads 1>&2")
           machine.succeed("curl -f https://example.com/PRIVOXY-FORCE/ads 1>&2")
 
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index ce3b3fbf3bf3..5f50a3f87d5d 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/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
@@ -35,7 +35,7 @@ let
     *      };
     *      exporterTest = ''
     *        wait_for_unit("prometheus-<exporterName>-exporter.service")
-    *        wait_for_open_port("1234")
+    *        wait_for_open_port(1234)
     *        succeed("curl -sSf 'localhost:1234/metrics'")
     *      '';
     *    };
@@ -52,7 +52,7 @@ let
     *    testScript = ''
     *      <exporterName>.start()
     *      <exporterName>.wait_for_unit("prometheus-<exporterName>-exporter.service")
-    *      <exporterName>.wait_for_open_port("1234")
+    *      <exporterName>.wait_for_open_port(1234)
     *      <exporterName>.succeed("curl -sSf 'localhost:1234/metrics'")
     *      <exporterName>.shutdown()
     *    '';
@@ -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"],
@@ -307,6 +307,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;
@@ -361,25 +374,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 +409,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(
@@ -557,10 +581,12 @@ let
         systemd.services.prometheus-mail-exporter = {
           after = [ "postfix.service" ];
           requires = [ "postfix.service" ];
-          preStart = ''
-            mkdir -p -m 0700 mail-exporter/new
-          '';
           serviceConfig = {
+            ExecStartPre = [
+              "${pkgs.writeShellScript "create-maildir" ''
+                mkdir -p -m 0700 mail-exporter/new
+              ''}"
+            ];
             ProtectHome = true;
             ReadOnlyPaths = "/";
             ReadWritePaths = "/var/spool/mail";
@@ -1060,13 +1086,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"'
         )
       '';
     };
@@ -1151,6 +1172,25 @@ 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{'")
+        succeed(
+          "echo 'test.udp:1|c' > /dev/udp/localhost/9125",
+          "curl http://localhost:9102/metrics | grep 'test_udp 1'",
+        )
+        succeed(
+          "echo 'test.tcp:1|c' > /dev/tcp/localhost/9125",
+          "curl http://localhost:9102/metrics | grep 'test_tcp 1'",
+        )
+      '';
+    };
+
     surfboard = {
       exporterConfig = {
         enable = true;
@@ -1179,21 +1219,21 @@ let
         enable = true;
 
         extraFlags = [
-          "--collector.enable-restart-count"
+          "--systemd.collector.enable-restart-count"
         ];
       };
       metricProvider = { };
       exporterTest = ''
         wait_for_unit("prometheus-systemd-exporter.service")
         wait_for_open_port(9558)
-        succeed(
+        wait_until_succeeds(
             "curl -sSf localhost:9558/metrics | grep '{}'".format(
                 'systemd_unit_state{name="basic.target",state="active",type="target"} 1'
             )
         )
         succeed(
             "curl -sSf localhost:9558/metrics | grep '{}'".format(
-                'systemd_service_restart_total{state="prometheus-systemd-exporter.service"} 0'
+                'systemd_service_restart_total{name="prometheus-systemd-exporter.service"} 0'
             )
         )
       '';
@@ -1218,15 +1258,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"'
         )
       '';
     };
@@ -1254,6 +1292,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;
@@ -1309,6 +1408,22 @@ let
           )
         '';
       };
+
+    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
@@ -1335,7 +1450,7 @@ mapAttrs
       '';
 
       meta = with maintainers; {
-        maintainers = [ willibutz elseym ];
+        maintainers = [ willibutz ];
       };
     }
   )))
diff --git a/nixos/tests/prowlarr.nix b/nixos/tests/prowlarr.nix
index 4cbca107568f..144cbd5fc95d 100644
--- a/nixos/tests/prowlarr.nix
+++ b/nixos/tests/prowlarr.nix
@@ -12,7 +12,7 @@ with lib;
 
   testScript = ''
     machine.wait_for_unit("prowlarr.service")
-    machine.wait_for_open_port("9696")
+    machine.wait_for_open_port(9696)
     machine.succeed("curl --fail http://localhost:9696/")
   '';
 })
diff --git a/nixos/tests/public-inbox.nix b/nixos/tests/public-inbox.nix
new file mode 100644
index 000000000000..7de40400fcbf
--- /dev/null
+++ b/nixos/tests/public-inbox.nix
@@ -0,0 +1,227 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  orga = "example";
+  domain = "${orga}.localdomain";
+
+  tls-cert = pkgs.runCommand "selfSignedCert" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
+      -subj '/CN=machine.${domain}'
+    install -D -t $out key.pem cert.pem
+  '';
+in
+{
+  name = "public-inbox";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ julm ];
+
+  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.
+    repositories = [
+      "user/repo1"
+      "user/repo2"
+    ];
+  in
+  {
+    virtualisation.diskSize = 1 * 1024;
+    virtualisation.memorySize = 1 * 1024;
+    networking.domain = domain;
+
+    security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+    # If using security.acme:
+    #security.acme.certs."${domain}".postRun = ''
+    #  systemctl try-restart public-inbox-nntpd public-inbox-imapd
+    #'';
+
+    services.public-inbox = {
+      enable = true;
+      postfix.enable = true;
+      openFirewall = true;
+      settings.publicinbox = {
+        css = [ "href=https://machine.${domain}/style/light.css" ];
+        nntpserver = [ "nntps://machine.${domain}" ];
+        wwwlisting = "match=domain";
+      };
+      mda = {
+        enable = true;
+        args = [ "--no-precheck" ]; # Allow Bcc:
+      };
+      http = {
+        enable = true;
+        port = "/run/public-inbox-http.sock";
+        #port = 8080;
+        args = ["-W0"];
+        mounts = [
+          "https://machine.${domain}/inbox"
+        ];
+      };
+      nntp = {
+        enable = true;
+        #port = 563;
+        args = ["-W0"];
+        cert = "${tls-cert}/cert.pem";
+        key = "${tls-cert}/key.pem";
+      };
+      imap = {
+        enable = true;
+        #port = 993;
+        args = ["-W0"];
+        cert = "${tls-cert}/cert.pem";
+        key = "${tls-cert}/key.pem";
+      };
+      inboxes = lib.recursiveUpdate (lib.genAttrs (map baseNameOf repositories) (repo: {
+        address = [
+          # Routed to the "public-inbox:" transport in services.postfix.transport
+          "${repo}@${domain}"
+        ];
+        description = ''
+          ${repo}@${domain} :
+          discussions about ${repo}.
+        '';
+        url = "https://machine.${domain}/inbox/${repo}";
+        newsgroup = "inbox.comp.${orga}.${repo}";
+        coderepo = [ repo ];
+      }))
+      {
+        repo2 = {
+          hide = [
+            "imap" # FIXME: doesn't work for IMAP as of public-inbox 1.6.1
+            "manifest"
+            "www"
+          ];
+        };
+      };
+      settings.coderepo = lib.listToAttrs (map (path: lib.nameValuePair (baseNameOf path) {
+        dir = "/var/lib/gitolite/repositories/${path}.git";
+        cgitUrl = "https://git.${domain}/${path}.git";
+      }) repositories);
+    };
+
+    # Use gitolite to store Git repositories listed in coderepo entries
+    services.gitolite = {
+      enable = true;
+      adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmoTOQnGqX+//us5oye8UuE+tQBx9QEM7PN13jrwgqY root@localhost";
+    };
+    systemd.services.public-inbox-httpd = {
+      serviceConfig.SupplementaryGroups = [ gitolite.group ];
+    };
+
+    # Use nginx as a reverse proxy for public-inbox-httpd
+    services.nginx = {
+      enable = true;
+      recommendedGzipSettings = true;
+      recommendedOptimisation = true;
+      recommendedTlsSettings = true;
+      recommendedProxySettings = true;
+      virtualHosts."machine.${domain}" = {
+        forceSSL = true;
+        sslCertificate = "${tls-cert}/cert.pem";
+        sslCertificateKey = "${tls-cert}/key.pem";
+        locations."/".return = "302 /inbox";
+        locations."= /inbox".return = "302 /inbox/";
+        locations."/inbox".proxyPass = "http://unix:${public-inbox.http.port}:/inbox";
+        # If using TCP instead of a Unix socket:
+        #locations."/inbox".proxyPass = "http://127.0.0.1:${toString public-inbox.http.port}/inbox";
+        # Referred to by settings.publicinbox.css
+        # See http://public-inbox.org/meta/_/text/color/
+        locations."= /style/light.css".alias = pkgs.writeText "light.css" ''
+          * { background:#fff; color:#000 }
+
+          a { color:#00f; text-decoration:none }
+          a:visited { color:#808 }
+
+          *.q { color:#008 }
+
+          *.add { color:#060 }
+          *.del {color:#900 }
+          *.head { color:#000 }
+          *.hunk { color:#960 }
+
+          .hl.num { color:#f30 } /* number */
+          .hl.esc { color:#f0f } /* escape character */
+          .hl.str { color:#f30 } /* string */
+          .hl.ppc { color:#c3c } /* preprocessor */
+          .hl.pps { color:#f30 } /* preprocessor string */
+          .hl.slc { color:#099 } /* single-line comment */
+          .hl.com { color:#099 } /* multi-line comment */
+          /* .hl.opt { color:#ccc } */ /* operator */
+          /* .hl.ipl { color:#ccc } */ /* interpolation */
+
+          /* keyword groups kw[a-z] */
+          .hl.kwa { color:#f90 }
+          .hl.kwb { color:#060 }
+          .hl.kwc { color:#f90 }
+          /* .hl.kwd { color:#ccc } */
+        '';
+      };
+    };
+
+    services.postfix = {
+      enable = true;
+      setSendmail = true;
+      #sslCert = "${tls-cert}/cert.pem";
+      #sslKey = "${tls-cert}/key.pem";
+      recipientDelimiter = "+";
+    };
+
+    environment.systemPackages = [
+      pkgs.mailutils
+      pkgs.openssl
+    ];
+
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("public-inbox-init.service")
+
+    # Very basic check that Gitolite can work;
+    # Gitolite is not needed for the rest of this testScript
+    machine.wait_for_unit("gitolite-init.service")
+
+    # List inboxes through public-inbox-httpd
+    machine.wait_for_unit("nginx.service")
+    machine.succeed("curl -L https://machine.${domain} | grep repo1@${domain}")
+    # The repo2 inbox is hidden
+    machine.fail("curl -L https://machine.${domain} | grep repo2@${domain}")
+    machine.wait_for_unit("public-inbox-httpd.service")
+
+    # Send a mail and read it through public-inbox-httpd
+    # Must work too when using a recipientDelimiter.
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("mail -t <${pkgs.writeText "mail" ''
+      Subject: Testing mail
+      From: root@localhost
+      To: repo1+extension@${domain}
+      Message-ID: <repo1@root-1>
+      Content-Type: text/plain; charset=utf-8
+      Content-Disposition: inline
+
+      This is a testing mail.
+    ''}")
+    machine.sleep(5)
+    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
+    machine.wait_for_open_port(993)
+    machine.wait_for_unit("public-inbox-imapd.service")
+    machine.succeed("openssl s_client -ign_eof -crlf -connect machine.${domain}:993 <${pkgs.writeText "imap-commands" ''
+      tag login anonymous@${domain} anonymous
+      tag SELECT INBOX.comp.${orga}.repo1.0
+      tag FETCH 1 (BODY[HEADER])
+      tag LOGOUT
+    ''} | grep '^Message-ID: <repo1@root-1>'")
+
+    # TODO: Read a mail through public-inbox-nntpd
+    #machine.wait_for_open_port(563)
+    #machine.wait_for_unit("public-inbox-nntpd.service")
+
+    # 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.'")
+  '';
+})
diff --git a/nixos/tests/pulseaudio.nix b/nixos/tests/pulseaudio.nix
index cfdc61bc6c2b..dc8e33ccd559 100644
--- a/nixos/tests/pulseaudio.nix
+++ b/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/nixos/tests/pykms.nix b/nixos/tests/pykms.nix
new file mode 100644
index 000000000000..14d776a2f113
--- /dev/null
+++ b/nixos/tests/pykms.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  {
+    name = "pykms-test";
+    meta.maintainers = with pkgs.lib.maintainers; [ zopieux ];
+
+    nodes.machine = { config, lib, pkgs, ... }: {
+      services.pykms.enable = true;
+    };
+
+    testScript = ''
+      machine.wait_for_unit("pykms.service")
+      machine.succeed("${pkgs.pykms}/bin/client")
+    '';
+  })
diff --git a/nixos/tests/quake3.nix b/nixos/tests/quake3.nix
new file mode 100644
index 000000000000..82af1af463d0
--- /dev/null
+++ b/nixos/tests/quake3.nix
@@ -0,0 +1,95 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+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: with pkgs.lib; let
+    allowPackageNames = [ "quake3-demodata" "quake3-pointrelease" ];
+    allowLicenses = [ pkgs.lib.licenses.unfreeRedistributable ];
+  in elem pkg.pname allowPackageNames &&
+     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 pkgs.stdenv.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/nixos/tests/rabbitmq.nix b/nixos/tests/rabbitmq.nix
index 831335d8c518..040679e68d98 100644
--- a/nixos/tests/rabbitmq.nix
+++ b/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;
@@ -22,6 +51,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.wait_until_succeeds(
         'su -s ${pkgs.runtimeShell} rabbitmq -c "rabbitmqctl status"'
     )
-    machine.wait_for_open_port("15672")
+    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/nixos/tests/radarr.nix b/nixos/tests/radarr.nix
index ed90025ac420..85fd6572f061 100644
--- a/nixos/tests/radarr.nix
+++ b/nixos/tests/radarr.nix
@@ -12,7 +12,7 @@ with lib;
 
   testScript = ''
     machine.wait_for_unit("radarr.service")
-    machine.wait_for_open_port("7878")
+    machine.wait_for_open_port(7878)
     machine.succeed("curl --fail http://localhost:7878/")
   '';
 })
diff --git a/nixos/tests/redis.nix b/nixos/tests/redis.nix
index 7b70c239ad6e..abea1657f3ea 100644
--- a/nixos/tests/redis.nix
+++ b/nixos/tests/redis.nix
@@ -30,7 +30,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     machine.wait_for_unit("redis-test")
 
     # The unnamed Redis server still opens a port for backward-compatibility
-    machine.wait_for_open_port("6379")
+    machine.wait_for_open_port(6379)
 
     machine.wait_for_file("${redis.servers."".unixSocket}")
     machine.wait_for_file("${redis.servers."test".unixSocket}")
diff --git a/nixos/tests/resolv.nix b/nixos/tests/resolv.nix
deleted file mode 100644
index f0aa7e42aaf3..000000000000
--- a/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/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 16979eab8217..3681c4cf190e 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -1,96 +1,156 @@
 import ./make-test-python.nix (
   { pkgs, ... }:
 
-    let
-      password = "some_password";
-      repository = "/tmp/restic-backup";
-      rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
-
-      passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
-      initialize = true;
-      paths = [ "/opt" ];
-      pruneOpts = [
-        "--keep-daily 2"
-        "--keep-weekly 1"
-        "--keep-monthly 1"
-        "--keep-yearly 99"
-      ];
-    in
-      {
-        name = "restic";
-
-        meta = with pkgs.lib.maintainers; {
-          maintainers = [ bbigras i077 ];
-        };
+  let
+    remoteRepository = "/tmp/restic-backup";
+    remoteFromFileRepository = "/tmp/restic-backup-from-file";
+    rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
+
+    backupPrepareCommand = ''
+      touch /opt/backupPrepareCommand
+      test ! -e /opt/backupCleanupCommand
+    '';
+
+    backupCleanupCommand = ''
+      rm /opt/backupPrepareCommand
+      touch /opt/backupCleanupCommand
+    '';
+
+    passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
+    paths = [ "/opt" ];
+    pruneOpts = [
+      "--keep-daily 2"
+      "--keep-weekly 1"
+      "--keep-monthly 1"
+      "--keep-yearly 99"
+    ];
+  in
+  {
+    name = "restic";
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bbigras i077 ];
+    };
 
-        nodes = {
-          server =
-            { pkgs, ... }:
-              {
-                services.restic.backups = {
-                  remotebackup = {
-                    inherit repository passwordFile initialize paths pruneOpts;
-                  };
-                  rclonebackup = {
-                    repository = rcloneRepository;
-                    rcloneConfig = {
-                      type = "local";
-                      one_file_system = true;
-                    };
-
-                    # This gets overridden by rcloneConfig.type
-                    rcloneConfigFile = pkgs.writeText "rclone.conf" ''
-                      [local]
-                      type=ftp
-                    '';
-                    inherit passwordFile initialize paths pruneOpts;
-                  };
-                  remoteprune = {
-                    inherit repository passwordFile;
-                    pruneOpts = [ "--keep-last 1" ];
-                  };
-                };
-
-                environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
+    nodes = {
+      server =
+        { pkgs, ... }:
+        {
+          services.restic.backups = {
+            remotebackup = {
+              inherit passwordFile paths pruneOpts backupPrepareCommand backupCleanupCommand;
+              repository = remoteRepository;
+              initialize = true;
+            };
+            remote-from-file-backup = {
+              inherit passwordFile paths pruneOpts;
+              initialize = true;
+              repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository;
+            };
+            rclonebackup = {
+              inherit passwordFile paths pruneOpts;
+              initialize = true;
+              repository = rcloneRepository;
+              rcloneConfig = {
+                type = "local";
+                one_file_system = true;
               };
+
+              # This gets overridden by rcloneConfig.type
+              rcloneConfigFile = pkgs.writeText "rclone.conf" ''
+                [local]
+                type=ftp
+              '';
+            };
+            remoteprune = {
+              inherit passwordFile;
+              repository = remoteRepository;
+              pruneOpts = [ "--keep-last 1" ];
+            };
+            custompackage = {
+              inherit passwordFile paths;
+              repository = "some-fake-repository";
+              package = pkgs.writeShellScriptBin "restic" ''
+                echo "$@" >> /tmp/fake-restic.log;
+              '';
+
+              pruneOpts = [ "--keep-last 1" ];
+              checkOpts = [ "--some-check-option" ];
+            };
+          };
+
+          environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
         };
+    };
+
+    testScript = ''
+      server.start()
+      server.wait_for_unit("dbus.socket")
+      server.fail(
+          "${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",
+      )
+      server.succeed(
+          # set up
+          "mkdir -p /opt",
+          "touch /opt/some_file",
+          "mkdir -p /tmp/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",
+          '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # 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 ${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 'check .* --some-check-option' /tmp/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",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-13 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-14 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-15 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-16 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          '${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 ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
 
-        testScript = ''
-          server.start()
-          server.wait_for_unit("dbus.socket")
-          server.fail(
-              "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots",
-              "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
-          )
-          server.succeed(
-              "mkdir -p /opt",
-              "touch /opt/some_file",
-              "mkdir -p /tmp/restic-rclone-backup",
-              "timedatectl set-time '2016-12-13 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-              "timedatectl set-time '2017-12-13 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-13 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-14 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-15 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-16 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
-              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
-              "systemctl start restic-backups-remoteprune.service",
-              '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-          )
-        '';
-      }
+      )
+    '';
+  }
 )
diff --git a/nixos/tests/retroarch.nix b/nixos/tests/retroarch.nix
index c506ed02da89..f4bf232ea725 100644
--- a/nixos/tests/retroarch.nix
+++ b/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/nixos/tests/riak.nix b/nixos/tests/riak.nix
deleted file mode 100644
index e75d40fa2569..000000000000
--- a/nixos/tests/riak.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-import ./make-test-python.nix ({ lib, pkgs, ... }: {
-  name = "riak";
-  meta = with lib.maintainers; {
-    maintainers = [ Br1ght0ne ];
-  };
-
-  nodes.machine = {
-    services.riak.enable = true;
-    services.riak.package = pkgs.riak;
-  };
-
-  testScript = ''
-    machine.start()
-
-    machine.wait_for_unit("riak")
-    machine.wait_until_succeeds("riak ping 2>&1")
-  '';
-})
diff --git a/nixos/tests/sanoid.nix b/nixos/tests/sanoid.nix
index 3bdbe0a8d8db..411ebcead9f6 100644
--- a/nixos/tests/sanoid.nix
+++ b/nixos/tests/sanoid.nix
@@ -34,6 +34,7 @@ in {
           autosnap = true;
         };
         datasets."pool/sanoid".use_template = [ "test" ];
+        datasets."pool/compat".useTemplate = [ "test" ];
         extraArgs = [ "--verbose" ];
       };
 
@@ -48,6 +49,15 @@ in {
           };
           # Take snapshot and sync
           "pool/syncoid".target = "root@target:pool/syncoid";
+
+          # 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" ];
+          };
         };
       };
     };
@@ -67,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",
     )
@@ -91,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"
@@ -105,6 +117,12 @@ in {
     source.systemctl("start --wait syncoid-pool-syncoid.service")
     target.succeed("cat /mnt/pool/syncoid/test.txt")
 
+    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/nixos/tests/schleuder.nix b/nixos/tests/schleuder.nix
new file mode 100644
index 000000000000..e57ef66bb8f9
--- /dev/null
+++ b/nixos/tests/schleuder.nix
@@ -0,0 +1,126 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in
+import ./make-test-python.nix {
+  name = "schleuder";
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmission = true;
+      tlsTrustedAuthorities = "${certs.ca.cert}";
+      sslCert = "${certs.${domain}.cert}";
+      sslKey = "${certs.${domain}.key}";
+      inherit domain;
+      destination = [ domain ];
+      localRecipients = [ "root" "alice" "bob" ];
+    };
+    services.schleuder = {
+      enable = true;
+      # Don't do it like this in production! The point of this setting
+      # is to allow loading secrets from _outside_ the world-readable
+      # Nix store.
+      extraSettingsFile = pkgs.writeText "schleuder-api-keys.yml" ''
+        api:
+          valid_api_keys:
+            - fnord
+      '';
+      lists = [ "security@${domain}" ];
+      settings.api = {
+        tls_cert_file = "${certs.${domain}.cert}";
+        tls_key_file = "${certs.${domain}.key}";
+      };
+    };
+
+    environment.systemPackages = [
+      pkgs.gnupg
+      pkgs.msmtp
+      (pkgs.writeScriptBin "do-test" ''
+        #!${pkgs.runtimeShell}
+        set -exuo pipefail
+
+        # Generate a GPG key with no passphrase and export it
+        sudo -u alice gpg --passphrase-fd 0 --batch --yes --quick-generate-key 'alice@${domain}' rsa4096 sign,encr < <(echo)
+        sudo -u alice gpg --armor --export alice@${domain} > alice.asc
+        # Create a new mailing list with alice as the owner, and alice's key
+        schleuder-cli list new security@${domain} alice@${domain} alice.asc
+
+        # Send an email from a non-member of the list. Use --auto-from so we don't have to specify who it's from twice.
+        msmtp --auto-from security@${domain} --host=${domain} --port=25 --tls --tls-starttls <<EOF
+          Subject: really big security issue!!
+          From: root@${domain}
+
+          I found a big security problem!
+        EOF
+
+        # Wait for delivery
+        (set +o pipefail; journalctl -f -n 1000 -u postfix | grep -m 1 'delivered to maildir')
+
+        # There should be exactly one email
+        mail=(/var/spool/mail/alice/new/*)
+        [[ "''${#mail[@]}" = 1 ]]
+
+        # Find the fingerprint of the mailing list key
+        read list_key_fp address < <(schleuder-cli keys list security@${domain} | grep security@)
+        schleuder-cli keys export security@${domain} $list_key_fp > list.asc
+
+        # Import the key into alice's keyring, so we can verify it as well as decrypting
+        sudo -u alice gpg --import <list.asc
+        # And perform the decryption.
+        sudo -u alice gpg -d $mail >decrypted
+        # And check that the text matches.
+        grep "big security problem" decrypted
+      '')
+
+      # For debugging:
+      # pkgs.vim pkgs.openssl pkgs.sqliteinteractive
+    ];
+
+    security.pki.certificateFiles = [ certs.ca.cert ];
+
+    # Since we don't have internet here, use dnsmasq to provide MX records from /etc/hosts
+    services.dnsmasq = {
+      enable = true;
+      settings.selfmx = true;
+    };
+
+    networking.extraHosts = ''
+      127.0.0.1 ${domain}
+    '';
+
+    # schleuder-cli's config is not quite optimal in several ways:
+    # - A fingerprint _must_ be pinned, it doesn't even have an option
+    #   to trust the PKI
+    # - It compares certificate fingerprints rather than key
+    #   fingerprints, so renewals break the pin (though that's not
+    #   relevant for this test)
+    # - It compares them as strings, which means we need to match the
+    #   expected format exactly. This means removing the :s and
+    #   lowercasing it.
+    # Refs:
+    # https://0xacab.org/schleuder/schleuder-cli/-/issues/16
+    # https://0xacab.org/schleuder/schleuder-cli/-/blob/f8895b9f47083d8c7b99a2797c93f170f3c6a3c0/lib/schleuder-cli/helper.rb#L230-238
+    systemd.tmpfiles.rules = let cliconfig = pkgs.runCommand "schleuder-cli.yml"
+      {
+        nativeBuildInputs = [ pkgs.jq pkgs.openssl ];
+      } ''
+      fp=$(openssl x509 -in ${certs.${domain}.cert} -noout -fingerprint -sha256 | cut -d = -f 2 | tr -d : | tr 'A-Z' 'a-z')
+      cat > $out <<EOF
+      host: localhost
+      port: 4443
+      tls_fingerprint: "$fp"
+      api_key: fnord
+      EOF
+    ''; in
+      [
+        "L+ /root/.schleuder-cli/schleuder-cli.yml - - - - ${cliconfig}"
+      ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_until_succeeds("nc -z localhost 4443")
+    machine.succeed("do-test")
+  '';
+}
diff --git a/nixos/tests/seafile.nix b/nixos/tests/seafile.nix
index 6eec8b1fbe55..78e735f4fed7 100644
--- a/nixos/tests/seafile.nix
+++ b/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/nixos/tests/shadow.nix b/nixos/tests/shadow.nix
index dd2a575b1935..baa2e5945c05 100644
--- a/nixos/tests/shadow.nix
+++ b/nixos/tests/shadow.nix
@@ -3,6 +3,8 @@ 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
 in import ./make-test-python.nix ({ pkgs, ... }: {
   name = "shadow";
   meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
@@ -27,6 +29,16 @@ 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;
+      };
     };
   };
 
@@ -39,9 +51,9 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         shadow.wait_until_succeeds("[ $(fgconsole) = 2 ]")
         shadow.wait_for_unit("getty@tty2.service")
         shadow.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
-        shadow.wait_until_tty_matches(2, "login: ")
+        shadow.wait_until_tty_matches("2", "login: ")
         shadow.send_chars("emma\n")
-        shadow.wait_until_tty_matches(2, "login: emma")
+        shadow.wait_until_tty_matches("2", "login: emma")
         shadow.wait_until_succeeds("pgrep login")
         shadow.sleep(2)
         shadow.send_chars("${password1}\n")
@@ -63,9 +75,9 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         shadow.wait_until_succeeds("[ $(fgconsole) = 3 ]")
         shadow.wait_for_unit("getty@tty3.service")
         shadow.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
-        shadow.wait_until_tty_matches(3, "login: ")
+        shadow.wait_until_tty_matches("3", "login: ")
         shadow.send_chars("emma\n")
-        shadow.wait_until_tty_matches(3, "login: emma")
+        shadow.wait_until_tty_matches("3", "login: emma")
         shadow.wait_until_succeeds("pgrep login")
         shadow.sleep(2)
         shadow.send_chars("${password1}\n")
@@ -81,16 +93,16 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         shadow.wait_until_succeeds("[ $(fgconsole) = 4 ]")
         shadow.wait_for_unit("getty@tty4.service")
         shadow.wait_until_succeeds("pgrep -f 'agetty.*tty4'")
-        shadow.wait_until_tty_matches(4, "login: ")
+        shadow.wait_until_tty_matches("4", "login: ")
         shadow.send_chars("emma\n")
-        shadow.wait_until_tty_matches(4, "login: emma")
+        shadow.wait_until_tty_matches("4", "login: emma")
         shadow.wait_until_succeeds("pgrep login")
         shadow.sleep(2)
         shadow.send_chars("${password1}\n")
-        shadow.wait_until_tty_matches(4, "Login incorrect")
-        shadow.wait_until_tty_matches(4, "login:")
+        shadow.wait_until_tty_matches("4", "Login incorrect")
+        shadow.wait_until_tty_matches("4", "login:")
         shadow.send_chars("emma\n")
-        shadow.wait_until_tty_matches(4, "login: emma")
+        shadow.wait_until_tty_matches("4", "login: emma")
         shadow.wait_until_succeeds("pgrep login")
         shadow.sleep(2)
         shadow.send_chars("${password3}\n")
@@ -109,11 +121,29 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         shadow.wait_until_succeeds("[ $(fgconsole) = 5 ]")
         shadow.wait_for_unit("getty@tty5.service")
         shadow.wait_until_succeeds("pgrep -f 'agetty.*tty5'")
-        shadow.wait_until_tty_matches(5, "login: ")
+        shadow.wait_until_tty_matches("5", "login: ")
         shadow.send_chars("layla\n")
-        shadow.wait_until_tty_matches(5, "login: layla")
+        shadow.wait_until_tty_matches("5", "login: layla")
         shadow.wait_until_succeeds("pgrep login")
         shadow.send_chars("${password2}\n")
-        shadow.wait_until_tty_matches(5, "login:")
+        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")
   '';
 })
diff --git a/nixos/tests/signal-desktop.nix b/nixos/tests/signal-desktop.nix
index fbe9cdf84d05..5e2b648c7cf5 100644
--- a/nixos/tests/signal-desktop.nix
+++ b/nixos/tests/signal-desktop.nix
@@ -60,7 +60,7 @@ in {
     )
     # Only SQLCipher should be able to read the encrypted DB:
     machine.fail(
-        "su - alice -c 'sqlite3 ~/.config/Signal/sql/db.sqlite .databases'"
+        "su - alice -c 'sqlite3 ~/.config/Signal/sql/db.sqlite .tables'"
     )
     print(machine.succeed(
         "su - alice -c 'sqlcipher ~/.config/Signal/sql/db.sqlite'"
diff --git a/nixos/tests/smokeping.nix b/nixos/tests/smokeping.nix
index ccacf60cfe4b..04f813964291 100644
--- a/nixos/tests/smokeping.nix
+++ b/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/nixos/tests/sonarr.nix b/nixos/tests/sonarr.nix
index 764a4d05b381..bdfc8916cb7f 100644
--- a/nixos/tests/sonarr.nix
+++ b/nixos/tests/sonarr.nix
@@ -12,7 +12,7 @@ with lib;
 
   testScript = ''
     machine.wait_for_unit("sonarr.service")
-    machine.wait_for_open_port("8989")
+    machine.wait_for_open_port(8989)
     machine.succeed("curl --fail http://localhost:8989/")
   '';
 })
diff --git a/nixos/tests/sourcehut.nix b/nixos/tests/sourcehut.nix
index 34a60247e00e..9caa1bcd98f5 100644
--- a/nixos/tests/sourcehut.nix
+++ b/nixos/tests/sourcehut.nix
@@ -35,7 +35,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
@@ -169,6 +169,45 @@ in
         oauth-client-id = "d07cb713d920702e";
       };
       settings.webhooks.private-key = pkgs.writeText "webhook-key" "Ra3IjxgFiwG9jxgp4WALQIZw/BMYt30xWiOsqD0J7EA=";
+      settings.mail = {
+        smtp-from = "root+hut@${domain}";
+        # WARNING: take care to keep pgp-privkey outside the Nix store in production,
+        # or use LoadCredentialEncrypted=
+        pgp-privkey = toString (pkgs.writeText "sourcehut.pgp-privkey" ''
+          -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+          lFgEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
+          Gk7hYQoAAP9X4oPmxxrHN8LewBpWITdBomNqlHoiP7mI0nz/BOPJHxEktDZuaXhv
+          cy90ZXN0cy9zb3VyY2VodXQgPHJvb3QraHV0QHNvdXJjZWh1dC5sb2NhbGRvbWFp
+          bj6IlwQTFgoAPxYhBPqjgjnL8RHN4JnADNicgXaYm0jJBQJioNE5AhsDBQkDwmcA
+          BgsJCAcDCgUVCgkICwUWAwIBAAIeBQIXgAAKCRDYnIF2mJtIySVCAP9e2nHsVHSi
+          2B1YGZpVG7Xf36vxljmMkbroQy+0gBPwRwEAq+jaiQqlbGhQ7R/HMFcAxBIVsq8h
+          Aw1rngsUd0o3dAicXQRioNE5EgorBgEEAZdVAQUBAQdAXZV2Sd5ZNBVTBbTGavMv
+          D6ORrUh8z7TI/3CsxCE7+yADAQgHAAD/c1RU9xH+V/uI1fE7HIn/zL0LUPpsuce2
+          cH++g4u3kBgTOYh+BBgWCgAmFiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg0TkC
+          GwwFCQPCZwAACgkQ2JyBdpibSMlKagD/cTre6p1m8QuJ7kwmCFRSz5tBzIuYMMgN
+          xtT7dmS91csA/35fWsOykSiFRojQ7ccCSUTHL7ApF2EbL968tP/D2hIG
+          =Hjoc
+          -----END PGP PRIVATE KEY BLOCK-----
+        '');
+        pgp-pubkey = pkgs.writeText "sourcehut.pgp-pubkey" ''
+          -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+          mDMEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
+          Gk7hYQq0Nm5peG9zL3Rlc3RzL3NvdXJjZWh1dCA8cm9vdCtodXRAc291cmNlaHV0
+          LmxvY2FsZG9tYWluPoiXBBMWCgA/FiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg
+          0TkCGwMFCQPCZwAGCwkIBwMKBRUKCQgLBRYDAgEAAh4FAheAAAoJENicgXaYm0jJ
+          JUIA/17acexUdKLYHVgZmlUbtd/fq/GWOYyRuuhDL7SAE/BHAQCr6NqJCqVsaFDt
+          H8cwVwDEEhWyryEDDWueCxR3Sjd0CLg4BGKg0TkSCisGAQQBl1UBBQEBB0BdlXZJ
+          3lk0FVMFtMZq8y8Po5GtSHzPtMj/cKzEITv7IAMBCAeIfgQYFgoAJhYhBPqjgjnL
+          8RHN4JnADNicgXaYm0jJBQJioNE5AhsMBQkDwmcAAAoJENicgXaYm0jJSmoA/3E6
+          3uqdZvELie5MJghUUs+bQcyLmDDIDcbU+3ZkvdXLAP9+X1rDspEohUaI0O3HAklE
+          xy+wKRdhGy/evLT/w9oSBg==
+          =pJD7
+          -----END PGP PUBLIC KEY BLOCK-----
+        '';
+        pgp-key-id = "0xFAA38239CBF111CDE099C00CD89C8176989B48C9";
+      };
     };
 
     networking.firewall.allowedTCPPorts = [ 443 ];
@@ -195,6 +234,7 @@ in
     # Testing metasrht
     machine.wait_for_unit("metasrht-api.service")
     machine.wait_for_unit("metasrht.service")
+    machine.wait_for_unit("metasrht-webhooks.service")
     machine.wait_for_open_port(5000)
     machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
     machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
@@ -206,7 +246,9 @@ in
     #machine.wait_for_unit("buildsrht-worker.service")
 
     # Testing gitsrht
+    machine.wait_for_unit("gitsrht-api.service")
     machine.wait_for_unit("gitsrht.service")
+    machine.wait_for_unit("gitsrht-webhooks.service")
     machine.succeed("curl -sL http://git.${domain} | grep git.${domain}")
   '';
 })
diff --git a/nixos/tests/spark/default.nix b/nixos/tests/spark/default.nix
index 025c5a5222e7..462f0d23a403 100644
--- a/nixos/tests/spark/default.nix
+++ b/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/nixos/tests/sqlite3-to-mysql.nix b/nixos/tests/sqlite3-to-mysql.nix
new file mode 100644
index 000000000000..029058187df3
--- /dev/null
+++ b/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/nixos/tests/sssd-ldap.nix b/nixos/tests/sssd-ldap.nix
index f816c0652cc5..ff83e96068a9 100644
--- a/nixos/tests/sssd-ldap.nix
+++ b/nixos/tests/sssd-ldap.nix
@@ -28,7 +28,7 @@ in import ./make-test-python.nix ({pkgs, ...}: {
             attrs = {
               objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
               olcDatabase = "{1}mdb";
-              olcDbDirectory = "/var/db/openldap";
+              olcDbDirectory = "/var/lib/openldap/db";
               olcSuffix = dbSuffix;
               olcRootDN = "cn=${ldapRootUser},${dbSuffix}";
               olcRootPW = ldapRootPassword;
@@ -67,6 +67,8 @@ in import ./make-test-python.nix ({pkgs, ...}: {
 
     services.sssd = {
       enable = true;
+      # just for testing purposes, don't put this into the Nix store in production!
+      environmentFile = "${pkgs.writeText "ldap-root" "LDAP_BIND_PW=${ldapRootPassword}"}";
       config = ''
         [sssd]
         config_file_version = 2
@@ -80,7 +82,7 @@ in import ./make-test-python.nix ({pkgs, ...}: {
         ldap_search_base = ${dbSuffix}
         ldap_default_bind_dn = cn=${ldapRootUser},${dbSuffix}
         ldap_default_authtok_type = password
-        ldap_default_authtok = ${ldapRootPassword}
+        ldap_default_authtok = $LDAP_BIND_PW
       '';
     };
   };
@@ -89,6 +91,11 @@ 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}")
   '';
 })
diff --git a/nixos/tests/stratis/default.nix b/nixos/tests/stratis/default.nix
new file mode 100644
index 000000000000..42daadd5fcaa
--- /dev/null
+++ b/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/nixos/tests/stratis/encryption.nix b/nixos/tests/stratis/encryption.nix
new file mode 100644
index 000000000000..a555ff8a8e85
--- /dev/null
+++ b/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/nixos/tests/stratis/simple.nix b/nixos/tests/stratis/simple.nix
new file mode 100644
index 000000000000..7357d71fc52b
--- /dev/null
+++ b/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 = [ 1024 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/nixos/tests/stunnel.nix b/nixos/tests/stunnel.nix
new file mode 100644
index 000000000000..22c087290fc7
--- /dev/null
+++ b/nixos/tests/stunnel.nix
@@ -0,0 +1,174 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  stunnelCommon = {
+    services.stunnel = {
+      enable = true;
+      user = "stunnel";
+    };
+    users.groups.stunnel = { };
+    users.users.stunnel = {
+      isSystemUser = true;
+      group = "stunnel";
+    };
+  };
+  makeCert = { config, pkgs, ... }: {
+    system.activationScripts.create-test-cert = stringAfter [ "users" ] ''
+      ${pkgs.openssl}/bin/openssl req -batch -x509 -newkey rsa -nodes -out /test-cert.pem -keyout /test-key.pem -subj /CN=${config.networking.hostName}
+      ( umask 077; cat /test-key.pem /test-cert.pem > /test-key-and-cert.pem )
+      chown stunnel /test-key.pem /test-key-and-cert.pem
+    '';
+  };
+  serverCommon = { pkgs, ... }: {
+    networking.firewall.allowedTCPPorts = [ 443 ];
+    services.stunnel.servers.https = {
+      accept = "443";
+      connect = 80;
+      cert = "/test-key-and-cert.pem";
+    };
+    systemd.services.simple-webserver = {
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        cd /etc/webroot
+        ${pkgs.python3}/bin/python -m http.server 80
+      '';
+    };
+  };
+  copyCert = src: dest: filename: ''
+    from shlex import quote
+    ${src}.wait_for_file("/test-key-and-cert.pem")
+    server_cert = ${src}.succeed("cat /test-cert.pem")
+    ${dest}.succeed("echo %s > ${filename}" % quote(server_cert))
+  '';
+
+in {
+  basicServer = makeTest {
+    name = "basicServer";
+
+    nodes = {
+      client = { };
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        environment.etc."webroot/index.html".text = "well met";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+
+      server.wait_for_unit("simple-webserver")
+      server.wait_for_unit("stunnel")
+
+      client.succeed("curl --fail --cacert /authorized-server-cert.crt https://server/ > out")
+      client.succeed('[[ "$(< out)" == "well met" ]]')
+    '';
+  };
+
+  serverAndClient = makeTest {
+    name = "serverAndClient";
+
+    nodes = {
+      client = {
+        imports = [ stunnelCommon ];
+        services.stunnel.clients = {
+          httpsClient = {
+            accept = "80";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+          };
+          httpsClientWithHostVerify = {
+            accept = "81";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+            verifyHostname = "server";
+          };
+          httpsClientWithHostVerifyFail = {
+            accept = "82";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+            verifyHostname = "wronghostname";
+          };
+        };
+      };
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        environment.etc."webroot/index.html".text = "hello there";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+
+      server.wait_for_unit("simple-webserver")
+      server.wait_for_unit("stunnel")
+
+      # In case stunnel came up before we got the server's cert copied over
+      client.succeed("systemctl reload-or-restart stunnel")
+
+      client.succeed("curl --fail http://localhost/ > out")
+      client.succeed('[[ "$(< out)" == "hello there" ]]')
+
+      client.succeed("curl --fail http://localhost:81/ > out")
+      client.succeed('[[ "$(< out)" == "hello there" ]]')
+
+      client.fail("curl --fail http://localhost:82/ > out")
+      client.succeed('[[ "$(< out)" == "" ]]')
+    '';
+  };
+
+  mutualAuth = makeTest {
+    name = "mutualAuth";
+
+    nodes = rec {
+      client = {
+        imports = [ makeCert stunnelCommon ];
+        services.stunnel.clients.authenticated-https = {
+          accept = "80";
+          connect = "server:443";
+          verifyPeer = true;
+          CAFile = "/authorized-server-cert.crt";
+          cert = "/test-cert.pem";
+          key = "/test-key.pem";
+        };
+      };
+      wrongclient = client;
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        services.stunnel.servers.https = {
+          CAFile = "/authorized-client-certs.crt";
+          verifyPeer = true;
+        };
+        environment.etc."webroot/index.html".text = "secret handshake";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+      ${copyCert "client" "server" "/authorized-client-certs.crt"}
+      ${copyCert "server" "wrongclient" "/authorized-server-cert.crt"}
+
+      # In case stunnel came up before we got the cross-certs in place
+      client.succeed("systemctl reload-or-restart stunnel")
+      server.succeed("systemctl reload-or-restart stunnel")
+      wrongclient.succeed("systemctl reload-or-restart stunnel")
+
+      server.wait_for_unit("simple-webserver")
+      client.fail("curl --fail --insecure https://server/ > out")
+      client.succeed('[[ "$(< out)" == "" ]]')
+      client.succeed("curl --fail http://localhost/ > out")
+      client.succeed('[[ "$(< out)" == "secret handshake" ]]')
+      wrongclient.fail("curl --fail http://localhost/ > out")
+      wrongclient.succeed('[[ "$(< out)" == "" ]]')
+    '';
+  };
+}
diff --git a/nixos/tests/swap-partition.nix b/nixos/tests/swap-partition.nix
new file mode 100644
index 000000000000..2279630b57b8
--- /dev/null
+++ b/nixos/tests/swap-partition.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "swap-partition";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    {
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.bootDevice = "/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
+          ${pkgs.util-linux}/bin/mkswap --label swap /dev/vda2
+        fi
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "ext4";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/dev/disk/by-label/swap";
+        }
+      ];
+    };
+
+  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'")
+  '';
+})
diff --git a/nixos/tests/sway.nix b/nixos/tests/sway.nix
index 8f95f2a030d1..52e2c7c99ec4 100644
--- a/nixos/tests/sway.nix
+++ b/nixos/tests/sway.nix
@@ -4,6 +4,12 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ primeos synthetica ];
   };
 
+  # testScriptWithTypes:49: error: Cannot call function of unknown type
+  #           (machine.succeed if succeed else machine.execute)(
+  #           ^
+  # Found 1 error in 1 file (checked 1 source file)
+  skipTypeCheck = true;
+
   nodes.machine = { config, ... }: {
     # Automatically login on tty1 as a normal user:
     imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index 0198866b6ff8..f891a2cb2f4c 100644
--- a/nixos/tests/switch-test.nix
+++ b/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/nixos/tests/sympa.nix b/nixos/tests/sympa.nix
index 76ca17d0a189..80daa4134f75 100644
--- a/nixos/tests/sympa.nix
+++ b/nixos/tests/sympa.nix
@@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
             webHost = "localhost";
           };
         };
-        listMasters = [ "joe@example.org" ];
+        listMasters = [ "bob@example.org" ];
         web.enable = true;
         web.https = false;
         database = {
diff --git a/nixos/tests/systemd-bpf.nix b/nixos/tests/systemd-bpf.nix
new file mode 100644
index 000000000000..e11347a2a817
--- /dev/null
+++ b/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/nixos/tests/systemd-confinement.nix b/nixos/tests/systemd-confinement.nix
index bde5b770ea50..428888d41a20 100644
--- a/nixos/tests/systemd-confinement.nix
+++ b/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/nixos/tests/systemd-coredump.nix b/nixos/tests/systemd-coredump.nix
new file mode 100644
index 000000000000..62137820878b
--- /dev/null
+++ b/nixos/tests/systemd-coredump.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+
+  crasher = pkgs.writeCBin "crasher" "int main;";
+
+  commonConfig = {
+    systemd.services.crasher.serviceConfig = {
+      ExecStart = "${crasher}/bin/crasher";
+      StateDirectory = "crasher";
+      WorkingDirectory = "%S/crasher";
+      Restart = "no";
+    };
+  };
+
+in
+
+{
+  name = "systemd-coredump";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ squalus ];
+  };
+
+  nodes.machine1 = { pkgs, lib, ... }: commonConfig;
+  nodes.machine2 = { pkgs, lib, ... }: lib.recursiveUpdate commonConfig {
+    systemd.coredump.enable = false;
+    systemd.package = pkgs.systemd.override {
+      withCoredump = false;
+    };
+  };
+
+  testScript = ''
+    with subtest("systemd-coredump enabled"):
+      machine1.wait_for_unit("multi-user.target")
+      machine1.wait_for_unit("systemd-coredump.socket")
+      machine1.systemctl("start crasher");
+      machine1.wait_until_succeeds("coredumpctl list | grep crasher", timeout=10)
+      machine1.fail("stat /var/lib/crasher/core")
+
+    with subtest("systemd-coredump disabled"):
+      machine2.systemctl("start crasher");
+      machine2.wait_until_succeeds("stat /var/lib/crasher/core", timeout=10)
+  '';
+})
diff --git a/nixos/tests/systemd-cryptenroll.nix b/nixos/tests/systemd-cryptenroll.nix
index 055ae7d1681f..9ee2d280fbbe 100644
--- a/nixos/tests/systemd-cryptenroll.nix
+++ b/nixos/tests/systemd-cryptenroll.nix
@@ -2,6 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "systemd-cryptenroll";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ ymatsiuk ];
+    broken = true; # times out after two hours, details -> https://github.com/NixOS/nixpkgs/issues/167994
   };
 
   nodes.machine = { pkgs, lib, ... }: {
diff --git a/nixos/tests/systemd-initrd-luks-fido2.nix b/nixos/tests/systemd-initrd-luks-fido2.nix
new file mode 100644
index 000000000000..133e552a3dc9
--- /dev/null
+++ b/nixos/tests/systemd-initrd-luks-fido2.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-luks-fido2";
+
+  nodes.machine = { pkgs, config, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = 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/vdc";
+          crypttabExtraOpts = [ "fido2-device=auto" ];
+        };
+      };
+      virtualisation.bootDevice = "/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/vdc -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --fido2-device=auto /dev/vdc |& 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/nixos/tests/systemd-initrd-luks-keyfile.nix b/nixos/tests/systemd-initrd-luks-keyfile.nix
index 970163c36a4f..25c0c5bd866d 100644
--- a/nixos/tests/systemd-initrd-luks-keyfile.nix
+++ b/nixos/tests/systemd-initrd-luks-keyfile.nix
@@ -32,7 +32,7 @@ in {
         };
       };
       virtualisation.bootDevice = "/dev/mapper/cryptroot";
-      boot.initrd.systemd.contents."/etc/cryptroot.key".source = keyfile;
+      boot.initrd.secrets."/etc/cryptroot.key" = keyfile;
     };
   };
 
diff --git a/nixos/tests/systemd-initrd-luks-password.nix b/nixos/tests/systemd-initrd-luks-password.nix
index e8e651f7b35f..55d0b4324b40 100644
--- a/nixos/tests/systemd-initrd-luks-password.nix
+++ b/nixos/tests/systemd-initrd-luks-password.nix
@@ -23,6 +23,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
         cryptroot2.device = "/dev/vdd";
       };
       virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      # test mounting device unlocked in initrd after switching root
+      virtualisation.fileSystems."/cryptroot2".device = "/dev/mapper/cryptroot2";
     };
   };
 
@@ -31,6 +33,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     machine.wait_for_unit("multi-user.target")
     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/vdd 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")
@@ -44,5 +48,6 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     machine.wait_for_unit("multi-user.target")
 
     assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+    assert "/dev/mapper/cryptroot2 on /cryptroot2 type ext4" in machine.succeed("mount")
   '';
 })
diff --git a/nixos/tests/systemd-initrd-luks-tpm2.nix b/nixos/tests/systemd-initrd-luks-tpm2.nix
new file mode 100644
index 000000000000..085088d2ee25
--- /dev/null
+++ b/nixos/tests/systemd-initrd-luks-tpm2.nix
@@ -0,0 +1,72 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-luks-tpm2";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = 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/vdc";
+          crypttabExtraOpts = [ "tpm2-device=auto" ];
+        };
+      };
+      virtualisation.bootDevice = "/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/vdc -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --tpm2-pcrs= --tpm2-device=auto /dev/vdc |& 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/nixos/tests/systemd-initrd-modprobe.nix b/nixos/tests/systemd-initrd-modprobe.nix
new file mode 100644
index 000000000000..bf635a10d0e9
--- /dev/null
+++ b/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/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix
index 959cc87c0f26..5d98114304b7 100644
--- a/nixos/tests/systemd-initrd-simple.nix
+++ b/nixos/tests/systemd-initrd-simple.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "systemd-initrd-simple";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     boot.initrd.systemd = {
       enable = true;
       emergencyAccess = true;
diff --git a/nixos/tests/systemd-machinectl.nix b/nixos/tests/systemd-machinectl.nix
index ce0c56a360e9..b8ed0c33e8e4 100644
--- a/nixos/tests/systemd-machinectl.nix
+++ b/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;
 
@@ -32,15 +33,25 @@ import ./make-test-python.nix (
       # use networkd to obtain systemd network setup
       networking.useNetworkd = true;
       networking.useDHCP = false;
-      services.resolved.enable = false;
-
-      # open DHCP server on interface to container
-      networking.firewall.trustedInterfaces = [ "ve-+" ];
 
       # do not try to access cache.nixos.org
       nix.settings.substituters = lib.mkForce [ ];
 
+      # auto-start container
+      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 = ''
@@ -60,11 +71,17 @@ import ./make-test-python.nix (
       machine.succeed("machinectl start ${containerName}");
       machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
 
+      # Test nss_mymachines without nscd
+      machine.succeed('LD_LIBRARY_PATH="/run/current-system/sw/lib" getent -s hosts:mymachines hosts ${containerName}');
+
+      # Test nss_mymachines via nscd
+      machine.succeed("getent hosts ${containerName}");
+
       # Test systemd-nspawn network configuration
       machine.succeed("ping -n -c 1 ${containerName}");
 
       # Test systemd-nspawn uses a user namespace
-      machine.succeed("test `stat ${containerRoot}/var/empty -c %u%g` != 00");
+      machine.succeed("test $(machinectl status ${containerName} | grep 'UID Shift: ' | wc -l) = 1")
 
       # Test systemd-nspawn reboot
       machine.succeed("machinectl shell ${containerName} /run/current-system/sw/bin/reboot");
@@ -74,8 +91,20 @@ import ./make-test-python.nix (
       machine.succeed("machinectl reboot ${containerName}");
       machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
 
+      # Restart machine
+      machine.shutdown()
+      machine.start()
+      machine.wait_for_unit("default.target");
+
+      # Test auto-start
+      machine.succeed("machinectl show ${containerName}")
+
       # Test machinectl stop
       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");
diff --git a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
index 37a89fc21e44..279b9aac8edb 100644
--- a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
+++ b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -7,10 +7,10 @@
 # - 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;
             };
 
diff --git a/nixos/tests/systemd-networkd-vrf.nix b/nixos/tests/systemd-networkd-vrf.nix
index 8a1580fc2ada..3c18f788e927 100644
--- a/nixos/tests/systemd-networkd-vrf.nix
+++ b/nixos/tests/systemd-networkd-vrf.nix
@@ -138,18 +138,18 @@ in {
   };
 
   testScript = ''
-    def compare_tables(expected, actual):
-        assert (
-            expected == actual
-        ), """
-        Routing tables don't match!
-        Expected:
-          {}
-        Actual:
-          {}
-        """.format(
-            expected, actual
-        )
+    import json
+
+    def compare(raw_json, to_compare):
+        data = json.loads(raw_json)
+        assert len(raw_json) >= len(to_compare)
+        for i, row in enumerate(to_compare):
+            actual = data[i]
+            assert len(row.keys()) > 0
+            for key, value in row.items():
+                assert value == actual[key], f"""
+                  In entry {i}, value {key}: got: {actual[key]}, expected {value}
+                """
 
 
     start_all()
@@ -159,23 +159,18 @@ in {
     node2.wait_for_unit("network.target")
     node3.wait_for_unit("network.target")
 
-    # NOTE: please keep in mind that the trailing whitespaces in the following strings
-    # are intentional as the output is compared against the raw `iproute2`-output.
-    # editorconfig-checker-disable
     client_ipv4_table = """
-    192.168.1.2 dev vrf1 proto static metric 100 
+    192.168.1.2 dev vrf1 proto static metric 100\x20
     192.168.2.3 dev vrf2 proto static metric 100
     """.strip()
     vrf1_table = """
-    broadcast 192.168.1.0 dev eth1 proto kernel scope link src 192.168.1.1 
-    192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1 
-    local 192.168.1.1 dev eth1 proto kernel scope host src 192.168.1.1 
+    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 = """
-    broadcast 192.168.2.0 dev eth2 proto kernel scope link src 192.168.2.1 
-    192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1 
-    local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1 
+    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
@@ -183,14 +178,28 @@ in {
     # Check that networkd properly configures the main routing table
     # and the routing tables for the VRF.
     with subtest("check vrf routing tables"):
-        compare_tables(
-            client_ipv4_table, client.succeed("ip -4 route list | head -n2").strip()
+        compare(
+            client.succeed("ip --json -4 route list"),
+            [
+                {"dst": "192.168.1.2", "dev": "vrf1", "metric": 100},
+                {"dst": "192.168.2.3", "dev": "vrf2", "metric": 100}
+            ]
         )
-        compare_tables(
-            vrf1_table, client.succeed("ip -4 route list table 23 | head -n4").strip()
+        compare(
+            client.succeed("ip --json -4 route list table 23"),
+            [
+                {"dst": "192.168.1.0/24", "dev": "eth1", "prefsrc": "192.168.1.1"},
+                {"type": "local", "dst": "192.168.1.1", "dev": "eth1", "prefsrc": "192.168.1.1"},
+                {"type": "broadcast", "dev": "eth1", "prefsrc": "192.168.1.1", "dst": "192.168.1.255"}
+            ]
         )
-        compare_tables(
-            vrf2_table, client.succeed("ip -4 route list table 42 | head -n4").strip()
+        compare(
+            client.succeed("ip --json -4 route list table 42"),
+            [
+                {"dst": "192.168.2.0/24", "dev": "eth2", "prefsrc": "192.168.2.1"},
+                {"type": "local", "dst": "192.168.2.1", "dev": "eth2", "prefsrc": "192.168.2.1"},
+                {"type": "broadcast", "dev": "eth2", "prefsrc": "192.168.2.1", "dst": "192.168.2.255"}
+            ]
         )
 
     # Ensure that other nodes are reachable via ICMP through the VRF.
diff --git a/nixos/tests/systemd-no-tainted.nix b/nixos/tests/systemd-no-tainted.nix
new file mode 100644
index 000000000000..f0504065f2a4
--- /dev/null
+++ b/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/nixos/tests/systemd-nspawn.nix b/nixos/tests/systemd-nspawn.nix
index 5bf55060d2e0..bc77ee2a4d15 100644
--- a/nixos/tests/systemd-nspawn.nix
+++ b/nixos/tests/systemd-nspawn.nix
@@ -10,8 +10,8 @@ let
       Key-Length: 1024
       Subkey-Type: ELG-E
       Subkey-Length: 1024
-      Name-Real: Joe Tester
-      Name-Email: joe@foo.bar
+      Name-Real: Bob Foobar
+      Name-Email: bob@foo.bar
       Expire-Date: 0
       # Do a commit here, so that we can later print "done"
       %commit
@@ -19,14 +19,21 @@ let
     EOF
     gpg --batch --generate-key foo
     rm $out/S.gpg-agent $out/S.gpg-agent.*
-    gpg --export joe@foo.bar -a > $out/pubkey.gpg
+    gpg --export bob@foo.bar -a > $out/pubkey.gpg
   '');
 
   nspawnImages = (pkgs.runCommand "localhost" { buildInputs = [ pkgs.coreutils pkgs.gnupg ]; } ''
     mkdir -p $out
     cd $out
+
+    # produce a testimage.raw
     dd if=/dev/urandom of=$out/testimage.raw bs=$((1024*1024+7)) count=5
-    sha256sum testimage.raw > SHA256SUMS
+
+    # produce a testimage2.tar.xz, containing the hello store path
+    tar cvJpf testimage2.tar.xz ${pkgs.hello}
+
+    # produce signature(s)
+    sha256sum testimage* > SHA256SUMS
     export GNUPGHOME="$(mktemp -d)"
     cp -R ${gpgKeyring}/* $GNUPGHOME
     gpg --batch --sign --detach-sign --output SHA256SUMS.gpg SHA256SUMS
@@ -56,5 +63,9 @@ in {
     client.succeed(
         "cmp /var/lib/machines/testimage.raw ${nspawnImages}/testimage.raw"
     )
+    client.succeed("machinectl pull-tar --verify=signature http://server/testimage2.tar.xz")
+    client.succeed(
+        "cmp /var/lib/machines/testimage2/${pkgs.hello}/bin/hello ${pkgs.hello}/bin/hello"
+    )
   '';
 })
diff --git a/nixos/tests/systemd-oomd.nix b/nixos/tests/systemd-oomd.nix
new file mode 100644
index 000000000000..55c4c1350000
--- /dev/null
+++ b/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/nixos/tests/systemd-portabled.nix b/nixos/tests/systemd-portabled.nix
new file mode 100644
index 000000000000..ef38258b0d86
--- /dev/null
+++ b/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/nixos/tests/systemd-shutdown.nix b/nixos/tests/systemd-shutdown.nix
index 9283489c2559..688cd6dd2c17 100644
--- a/nixos/tests/systemd-shutdown.nix
+++ b/nixos/tests/systemd-shutdown.nix
@@ -1,4 +1,6 @@
-import ./make-test-python.nix ({ pkgs, systemdStage1 ? false, ...} : {
+import ./make-test-python.nix ({ pkgs, systemdStage1 ? false, ...} : let
+  msg = "Shutting down NixOS";
+in {
   name = "systemd-shutdown";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ das_j ];
@@ -6,7 +8,9 @@ import ./make-test-python.nix ({ pkgs, systemdStage1 ? false, ...} : {
 
   nodes.machine = {
     imports = [ ../modules/profiles/minimal.nix ];
-    boot.initrd.systemd.enable = systemdStage1;
+    systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/shutdown-message".source = pkgs.writeShellScript "shutdown-message" ''
+      echo "${msg}"
+    '';
   };
 
   testScript = ''
@@ -14,7 +18,8 @@ import ./make-test-python.nix ({ pkgs, systemdStage1 ? false, ...} : {
     # .shutdown() would wait for the machine to power off
     machine.succeed("systemctl poweroff")
     # Message printed by systemd-shutdown
-    machine.wait_for_console_text("All filesystems, swaps, loop devices, MD devices and DM devices detached.")
+    machine.wait_for_console_text("Unmounting '/oldroot'")
+    machine.wait_for_console_text("${msg}")
     # Don't try to sync filesystems
     machine.booted = False
   '';
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index 3317823e03f7..3c36291b733d 100644
--- a/nixos/tests/systemd.nix
+++ b/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/nixos/tests/tandoor-recipes.nix b/nixos/tests/tandoor-recipes.nix
new file mode 100644
index 000000000000..54456238fe63
--- /dev/null
+++ b/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/nixos/tests/tayga.nix b/nixos/tests/tayga.nix
new file mode 100644
index 000000000000..44974f6efea8
--- /dev/null
+++ b/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/nixos/tests/teleport.nix b/nixos/tests/teleport.nix
index 15b16e44409d..34bf1bc0c70d 100644
--- a/nixos/tests/teleport.nix
+++ b/nixos/tests/teleport.nix
@@ -72,9 +72,9 @@ in
     nodes = { inherit minimal; };
 
     testScript = ''
-      minimal.wait_for_open_port("3025")
-      minimal.wait_for_open_port("3080")
-      minimal.wait_for_open_port("3022")
+      minimal.wait_for_open_port(3025)
+      minimal.wait_for_open_port(3080)
+      minimal.wait_for_open_port(3022)
     '';
   };
 
@@ -86,12 +86,12 @@ in
 
     testScript = ''
       with subtest("teleport ready"):
-          server.wait_for_open_port("3025")
-          client.wait_for_open_port("3022")
+          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")
+          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.'")
     '';
diff --git a/nixos/tests/terminal-emulators.nix b/nixos/tests/terminal-emulators.nix
index 6ea0f1c18725..4269d05056d8 100644
--- a/nixos/tests/terminal-emulators.nix
+++ b/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;
 
@@ -197,6 +199,7 @@ in mapAttrs (name: { pkg, executable ? name, cmd ? "SHELL=$command ${executable}
 
     with subtest("have the terminal display a colour"):
         # We run this command in the background
+        assert machine.shell is not None
         machine.shell.send(b"(run-in-this-term display-colour |& systemd-cat -t terminal) &\n")
 
         with machine.nested("Waiting for the screen to have pink on it:"):
diff --git a/nixos/tests/thelounge.nix b/nixos/tests/thelounge.nix
index e9b85685bf2d..8d5a37d46c46 100644
--- a/nixos/tests/thelounge.nix
+++ b/nixos/tests/thelounge.nix
@@ -1,4 +1,6 @@
 import ./make-test-python.nix {
+  name = "thelounge";
+
   nodes = {
     private = { config, pkgs, ... }: {
       services.thelounge = {
diff --git a/nixos/tests/tmate-ssh-server.nix b/nixos/tests/tmate-ssh-server.nix
new file mode 100644
index 000000000000..e7f94db9bfcf
--- /dev/null
+++ b/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/nixos/tests/tracee.nix b/nixos/tests/tracee.nix
new file mode 100644
index 000000000000..72e82ec0b7ed
--- /dev/null
+++ b/nixos/tests/tracee.nix
@@ -0,0 +1,49 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tracee-integration";
+  nodes = {
+    machine = { config, pkgs, ... }: {
+      # EventFilters/trace_only_events_from_new_containers requires docker
+      # podman with docker compat will suffice
+      virtualisation.podman.enable = true;
+      virtualisation.podman.dockerCompat = true;
+
+      environment.systemPackages = [
+        # build the go integration tests as a binary
+        (pkgs.tracee.overrideAttrs (oa: {
+          pname = oa.pname + "-integration";
+          patches = oa.patches or [] ++ [
+            # change the prefix from /usr/bin to /run to find nix processes
+            ../../pkgs/tools/security/tracee/test-EventFilters-prefix-nix-friendly.patch
+          ];
+          buildPhase = ''
+            runHook preBuild
+            # just build the static lib we need for the go test binary
+            make $makeFlags ''${enableParallelBuilding:+-j$NIX_BUILD_CORES -l$NIX_BUILD_CORES} bpf-core ./dist/btfhub
+
+            # remove the /usr/bin prefix to work with the patch above
+            substituteInPlace tests/integration/integration_test.go \
+              --replace "/usr/bin/ls" "ls"
+
+            # 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;
+          installPhase = ''
+            mkdir -p $out/bin
+            cp $GOPATH/tracee-integration $out/bin
+          '';
+          doInstallCheck = false;
+        }))
+      ];
+    };
+  };
+
+  testScript = ''
+    with subtest("run integration tests"):
+      # EventFilters/trace_only_events_from_new_containers also requires a container called "alpine"
+      machine.succeed('tar cv -C ${pkgs.pkgsStatic.busybox} . | podman import - alpine --change ENTRYPOINT=sleep')
+
+      print(machine.succeed('TRC_BIN="${pkgs.tracee}" tracee-integration -test.v'))
+  '';
+})
diff --git a/nixos/tests/traefik.nix b/nixos/tests/traefik.nix
index 1d6c0a479ef6..989ec390c060 100644
--- a/nixos/tests/traefik.nix
+++ b/nixos/tests/traefik.nix
@@ -11,14 +11,20 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       environment.systemPackages = [ pkgs.curl ];
     };
     traefik = { config, pkgs, ... }: {
-      virtualisation.oci-containers.containers.nginx = {
-        extraOptions = [
-          "-l" "traefik.enable=true"
-          "-l" "traefik.http.routers.nginx.entrypoints=web"
-          "-l" "traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)"
-        ];
-        image = "nginx-container";
-        imageFile = pkgs.dockerTools.examples.nginx;
+      virtualisation.oci-containers = {
+        backend = "docker";
+        containers.nginx = {
+          extraOptions = [
+            "-l"
+            "traefik.enable=true"
+            "-l"
+            "traefik.http.routers.nginx.entrypoints=web"
+            "-l"
+            "traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)"
+          ];
+          image = "nginx-container";
+          imageFile = pkgs.dockerTools.examples.nginx;
+        };
       };
 
       networking.firewall.allowedTCPPorts = [ 80 ];
diff --git a/nixos/tests/upnp.nix b/nixos/tests/upnp.nix
index 451c8607d0eb..af7cc1fe2413 100644
--- a/nixos/tests/upnp.nix
+++ b/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/nixos/tests/uptermd.nix b/nixos/tests/uptermd.nix
new file mode 100644
index 000000000000..429e3c9dd5ff
--- /dev/null
+++ b/nixos/tests/uptermd.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+
+let
+  client = {pkgs, ...}:{
+    environment.systemPackages = [ pkgs.upterm ];
+  };
+in
+{
+  name = "uptermd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fleaz ];
+  };
+
+  nodes = {
+    server = {config, ...}: {
+      services.uptermd = {
+        enable = true;
+        openFirewall = true;
+        port = 1337;
+      };
+    };
+    client1 = client;
+    client2 = client;
+  };
+
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("uptermd.service")
+    server.wait_for_unit("network-online.target")
+
+    # wait for upterm port to be reachable
+    client1.wait_until_succeeds("nc -z -v server 1337")
+
+    # Add SSH hostkeys from the server to both clients
+    # uptermd needs an '@cert-authority entry so we need to modify the known_hosts file
+    client1.execute("mkdir -p ~/.ssh && ssh -o StrictHostKeyChecking=no -p 1337 server ls")
+    client1.execute("echo @cert-authority $(cat ~/.ssh/known_hosts) > ~/.ssh/known_hosts")
+    client2.execute("mkdir -p ~/.ssh && ssh -o StrictHostKeyChecking=no -p 1337 server ls")
+    client2.execute("echo @cert-authority $(cat ~/.ssh/known_hosts) > ~/.ssh/known_hosts")
+
+    client1.wait_for_unit("multi-user.target")
+    client1.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    client1.wait_until_tty_matches("1", "login: ")
+    client1.send_chars("root\n")
+    client1.wait_until_succeeds("pgrep -u root bash")
+
+    client1.execute("ssh-keygen -t ed25519 -N \"\" -f /root/.ssh/id_ed25519")
+    client1.send_chars("TERM=xterm upterm host --server ssh://server:1337 --force-command hostname -- bash > /tmp/session-details\n")
+    client1.wait_for_file("/tmp/session-details")
+    client1.send_key("q")
+
+    # uptermd can't connect if we don't have a keypair
+    client2.execute("ssh-keygen -t ed25519 -N \"\" -f /root/.ssh/id_ed25519")
+
+    # Grep the ssh connect command from the output of 'upterm host'
+    ssh_command = client1.succeed("grep 'SSH Session' /tmp/session-details | cut -d':' -f2-").strip()
+
+    # Connect with client2. Because we used '--force-command hostname' we should get "client1" as the output
+    output = client2.succeed(ssh_command)
+
+    assert output.strip() == "client1"
+  '';
+})
diff --git a/nixos/tests/uptime-kuma.nix b/nixos/tests/uptime-kuma.nix
new file mode 100644
index 000000000000..3d588d73cdb5
--- /dev/null
+++ b/nixos/tests/uptime-kuma.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "uptime-kuma";
+  meta.maintainers = with 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/nixos/tests/user-activation-scripts.nix b/nixos/tests/user-activation-scripts.nix
index 934573578187..5df072ce0508 100644
--- a/nixos/tests/user-activation-scripts.nix
+++ b/nixos/tests/user-activation-scripts.nix
@@ -19,9 +19,9 @@ import ./make-test-python.nix ({ lib, ... }: {
 
     machine.wait_for_unit("multi-user.target")
     machine.wait_for_unit("getty@tty1.service")
-    machine.wait_until_tty_matches(1, "login: ")
+    machine.wait_until_tty_matches("1", "login: ")
     machine.send_chars("alice\n")
-    machine.wait_until_tty_matches(1, "Password: ")
+    machine.wait_until_tty_matches("1", "Password: ")
     machine.send_chars("pass1\n")
     machine.send_chars("touch login-ok\n")
     machine.wait_for_file("/home/alice/login-ok")
diff --git a/nixos/tests/user-home-mode.nix b/nixos/tests/user-home-mode.nix
new file mode 100644
index 000000000000..070cb0b75cc9
--- /dev/null
+++ b/nixos/tests/user-home-mode.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "user-home-mode";
+  meta = with lib.maintainers; { maintainers = [ fbeffa ]; };
+
+  nodes.machine = {
+    users.users.alice = {
+      initialPassword = "pass1";
+      isNormalUser = true;
+    };
+    users.users.bob = {
+      initialPassword = "pass2";
+      isNormalUser = true;
+      homeMode = "750";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("getty@tty1.service")
+    machine.wait_until_tty_matches("1", "login: ")
+    machine.send_chars("alice\n")
+    machine.wait_until_tty_matches("1", "Password: ")
+    machine.send_chars("pass1\n")
+    machine.succeed('[ "$(stat -c %a /home/alice)" == "700" ]')
+    machine.succeed('[ "$(stat -c %a /home/bob)" == "750" ]')
+  '';
+})
diff --git a/nixos/tests/v2ray.nix b/nixos/tests/v2ray.nix
index fb36ea8557d5..9eee962c64e4 100644
--- a/nixos/tests/v2ray.nix
+++ b/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/nixos/tests/varnish.nix b/nixos/tests/varnish.nix
new file mode 100644
index 000000000000..9dcdeec9d8c8
--- /dev/null
+++ b/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/nixos/tests/vault-dev.nix b/nixos/tests/vault-dev.nix
new file mode 100644
index 000000000000..ba9a1015cc13
--- /dev/null
+++ b/nixos/tests/vault-dev.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "vault-dev";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 mic92 ];
+  };
+  nodes.machine = { pkgs, config, ... }: {
+    environment.systemPackages = [ pkgs.vault ];
+    environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
+    environment.variables.VAULT_TOKEN = "phony-secret";
+
+    services.vault = {
+      enable = true;
+      dev = true;
+      devRootTokenID = config.environment.variables.VAULT_TOKEN;
+    };
+  };
+
+  testScript = ''
+    import json
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("vault.service")
+    machine.wait_for_open_port(8200)
+    out = machine.succeed("vault status -format=json")
+    print(out)
+    status = json.loads(out)
+    assert status.get("initialized") == True
+    machine.succeed("vault kv put secret/foo bar=baz")
+    out = machine.succeed("vault kv get -format=json secret/foo")
+    print(out)
+    status = json.loads(out)
+    assert status.get("data", {}).get("data", {}).get("bar") == "baz"
+  '';
+})
diff --git a/nixos/tests/vaultwarden.nix b/nixos/tests/vaultwarden.nix
index 814d8d7c0ab3..c0e1d0585b93 100644
--- a/nixos/tests/vaultwarden.nix
+++ b/nixos/tests/vaultwarden.nix
@@ -74,7 +74,10 @@ let
             services.vaultwarden = {
               enable = true;
               dbBackend = backend;
-              config.rocketPort = 80;
+              config = {
+                rocketAddress = "0.0.0.0";
+                rocketPort = 80;
+              };
             };
 
             networking.firewall.allowedTCPPorts = [ 80 ];
@@ -84,7 +87,12 @@ let
                 testRunner = pkgs.writers.writePython3Bin "test-runner"
                   {
                     libraries = [ pkgs.python3Packages.selenium ];
+                    flakeIgnore = [
+                      "E501"
+                    ];
                   } ''
+
+                  from selenium.webdriver.common.by import By
                   from selenium.webdriver import Firefox
                   from selenium.webdriver.firefox.options import Options
                   from selenium.webdriver.support.ui import WebDriverWait
@@ -101,40 +109,40 @@ let
 
                   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}'
                   )
 
-                  driver.find_element_by_xpath("//button[contains(., 'Submit')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Create Account')]").click()
 
                   wait.until_not(EC.title_contains("Create Account"))
 
-                  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("My Vault"))
+                  wait.until(EC.title_contains("Bitwarden Web Vault"))
 
-                  driver.find_element_by_xpath("//button[contains(., 'Add Item')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Add Item')]").click()
 
-                  driver.find_element_by_css_selector('input#name').send_keys(
-                    'secrets'
+                  driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys(
+                      'secrets'
                   )
-                  driver.find_element_by_css_selector('input#loginPassword').send_keys(
-                    '${storedPassword}'
+                  driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys(
+                      '${storedPassword}'
                   )
 
-                  driver.find_element_by_xpath("//button[contains(., 'Save')]").click()
+                  driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click()
                 '';
               in
               [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
@@ -156,7 +164,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/nixos/tests/vector.nix b/nixos/tests/vector.nix
index ecf94e33ff17..9309f6a14dd7 100644
--- a/nixos/tests/vector.nix
+++ b/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"; };
             };
           };
         };
diff --git a/nixos/tests/vengi-tools.nix b/nixos/tests/vengi-tools.nix
index 8b80a13384e5..fd7567991487 100644
--- a/nixos/tests/vengi-tools.nix
+++ b/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("Palette")
       machine.screenshot("screen")
     '';
 })
diff --git a/nixos/tests/vikunja.nix b/nixos/tests/vikunja.nix
index bd884b37f4f9..2f6c4c1f4661 100644
--- a/nixos/tests/vikunja.nix
+++ b/nixos/tests/vikunja.nix
@@ -1,9 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "vikunja";
 
-  meta = with lib.maintainers; {
-    maintainers = [ em0lar ];
-  };
+  meta.maintainers = with lib.maintainers; [ leona ];
 
   nodes = {
     vikunjaSqlite = { ... }: {
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index 27093aab96ee..0be22bdcaea6 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -3,18 +3,9 @@
   pkgs ? import ../.. { inherit system config; },
   debug ? false,
   enableUnfree ? false,
-  # Nested KVM virtualization (https://www.linux-kvm.org/page/Nested_Guests)
-  # requires a modprobe flag on the build machine: (kvm-amd for AMD CPUs)
-  #   boot.extraModprobeConfig = "options kvm-intel nested=Y";
-  # Without this VirtualBox will use SW virtualization and will only be able
-  # to run 32-bit guests.
-  useKvmNestedVirt ? false,
-  # Whether to run 64-bit guests instead of 32-bit. Requires nested KVM.
-  use64bitGuest ? false
+  use64bitGuest ? true
 }:
 
-assert use64bitGuest -> useKvmNestedVirt;
-
 with import ../lib/testing-python.nix { inherit system pkgs; };
 with pkgs.lib;
 
@@ -26,7 +17,8 @@ let
       #!${pkgs.runtimeShell} -xe
       export PATH="${lib.makeBinPath [ pkgs.coreutils pkgs.util-linux ]}"
 
-      mkdir -p /run/dbus
+      mkdir -p /run/dbus /var
+      ln -s /run /var
       cat > /etc/passwd <<EOF
       root:x:0:0::/root:/bin/false
       messagebus:x:1:1::/run/dbus:/bin/false
@@ -36,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}
@@ -200,6 +192,7 @@ let
       systemd.services."vboxtestlog-${name}@" = {
         description = "VirtualBox Test Machine Log For ${name}";
         serviceConfig.StandardInput = "socket";
+        serviceConfig.StandardOutput = "journal";
         serviceConfig.SyslogIdentifier = "GUEST-${name}";
         serviceConfig.ExecStart = "${pkgs.coreutils}/bin/cat";
       };
@@ -222,10 +215,11 @@ let
               machine.execute(ru("VBoxManage controlvm ${name} poweroff"))
           machine.succeed("rm -rf ${sharePath}")
           machine.succeed("mkdir -p ${sharePath}")
-          machine.succeed("chown alice.users ${sharePath}")
+          machine.succeed("chown alice:users ${sharePath}")
 
 
       def create_vm_${name}():
+          cleanup_${name}()
           vbm("createvm --name ${name} ${createFlags}")
           vbm("modifyvm ${name} ${vmFlags}")
           vbm("setextradata ${name} VBoxInternal/PDM/HaltOnReset 1")
@@ -233,7 +227,6 @@ let
           vbm("storageattach ${name} ${diskFlags}")
           vbm("sharedfolder add ${name} ${sharedFlags}")
           vbm("sharedfolder add ${name} ${nixstoreFlags}")
-          cleanup_${name}()
 
           ${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"}
 
@@ -317,10 +310,7 @@ let
   ];
 
   dhcpScript = pkgs: ''
-    ${pkgs.dhcp}/bin/dhclient \
-      -lf /run/dhcp.leases \
-      -pf /run/dhclient.pid \
-      -v eth0 eth1
+    ${pkgs.dhcpcd}/bin/dhcpcd eth0 eth1
 
     otherIP="$(${pkgs.netcat}/bin/nc -l 1234 || :)"
     ${pkgs.iputils}/bin/ping -I eth1 -c1 "$otherIP"
@@ -359,8 +349,7 @@ let
         vmConfigs = mapAttrsToList mkVMConf vms;
       in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs;
       virtualisation.memorySize = 2048;
-      virtualisation.qemu.options =
-        if useKvmNestedVirt then ["-cpu" "kvm64,vmx=on"] else [];
+      virtualisation.qemu.options = ["-cpu" "kvm64,svm=on,vmx=on"];
       virtualisation.virtualbox.host.enable = true;
       test-support.displayManager.auto.user = "alice";
       users.users.alice.extraGroups = let
@@ -468,7 +457,7 @@ in mapAttrs (mkVBoxTest false vboxVMs) {
 
   headless = ''
     create_vm_headless()
-    machine.succeed(ru("VBoxHeadless --startvm headless & disown %1"))
+    machine.succeed(ru("VBoxHeadless --startvm headless >&2 & disown %1"))
     wait_for_startup_headless()
     wait_for_vm_boot_headless()
     shutdown_vm_headless()
@@ -476,6 +465,8 @@ in mapAttrs (mkVBoxTest false vboxVMs) {
   '';
 
   host-usb-permissions = ''
+    import sys
+
     user_usb = remove_uuids(vbm("list usbhost"))
     print(user_usb, file=sys.stderr)
     root_usb = remove_uuids(machine.succeed("VBoxManage list usbhost"))
diff --git a/nixos/tests/vscodium.nix b/nixos/tests/vscodium.nix
index 496c52b78387..37bb649889b4 100644
--- a/nixos/tests/vscodium.nix
+++ b/nixos/tests/vscodium.nix
@@ -32,6 +32,14 @@ let
         maintainers = [ synthetica turion ];
       };
       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():
@@ -62,15 +70,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/nixos/tests/vsftpd.nix b/nixos/tests/vsftpd.nix
index 4bea27f0eb10..6eaf32b22583 100644
--- a/nixos/tests/vsftpd.nix
+++ b/nixos/tests/vsftpd.nix
@@ -29,7 +29,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   testScript = ''
     client.start()
     server.wait_for_unit("vsftpd")
-    server.wait_for_open_port("21")
+    server.wait_for_open_port(21)
 
     client.succeed("curl -u ftp-test-user:ftp-test-password ftp://server")
     client.succeed('echo "this is a test" > /tmp/test.file.up')
diff --git a/nixos/tests/warzone2100.nix b/nixos/tests/warzone2100.nix
new file mode 100644
index 000000000000..568e04a46999
--- /dev/null
+++ b/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/nixos/tests/web-apps/healthchecks.nix b/nixos/tests/web-apps/healthchecks.nix
new file mode 100644
index 000000000000..41c40cd5dd8d
--- /dev/null
+++ b/nixos/tests/web-apps/healthchecks.nix
@@ -0,0 +1,42 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "healthchecks";
+
+  meta = with lib.maintainers; {
+    maintainers = [ phaer ];
+  };
+
+  nodes.machine = { ... }: {
+    services.healthchecks = {
+      enable = true;
+      settings = {
+        SITE_NAME = "MyUniqueInstance";
+        COMPRESS_ENABLED = "True";
+        SECRET_KEY_FILE = pkgs.writeText "secret"
+          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("healthchecks.target")
+    machine.wait_until_succeeds("journalctl --since -1m --unit healthchecks --grep Listening")
+
+    with subtest("Home screen loads"):
+        machine.succeed(
+            "curl -sSfL http://localhost:8000 | grep '<title>Sign In'"
+        )
+
+    with subtest("Setting SITE_NAME via freeform option works"):
+        machine.succeed(
+            "curl -sSfL http://localhost:8000 | grep 'MyUniqueInstance</title>'"
+        )
+
+    with subtest("Manage script works"):
+        # "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/nixos/tests/web-apps/mastodon.nix b/nixos/tests/web-apps/mastodon.nix
deleted file mode 100644
index 279a1c59169f..000000000000
--- a/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/nixos/tests/web-apps/mastodon/default.nix b/nixos/tests/web-apps/mastodon/default.nix
new file mode 100644
index 000000000000..411ebfcd731b
--- /dev/null
+++ b/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/nixos/tests/web-apps/mastodon/remote-postgresql.nix b/nixos/tests/web-apps/mastodon/remote-postgresql.nix
new file mode 100644
index 000000000000..2fd3983e13ec
--- /dev/null
+++ b/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/nixos/tests/web-apps/mastodon/script.nix b/nixos/tests/web-apps/mastodon/script.nix
new file mode 100644
index 000000000000..cdb1d4379b64
--- /dev/null
+++ b/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.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/nixos/tests/web-apps/mastodon/standard.nix b/nixos/tests/web-apps/mastodon/standard.nix
new file mode 100644
index 000000000000..14311afea3f7
--- /dev/null
+++ b/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/nixos/tests/web-apps/peering-manager.nix b/nixos/tests/web-apps/peering-manager.nix
new file mode 100644
index 000000000000..56b7eebfadff
--- /dev/null
+++ b/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/nixos/tests/web-apps/peertube.nix b/nixos/tests/web-apps/peertube.nix
index d42b4e3d677b..ecc45bff2e2c 100644
--- a/nixos/tests/web-apps/peertube.nix
+++ b/nixos/tests/web-apps/peertube.nix
@@ -11,7 +11,7 @@ import ../make-test-python.nix ({pkgs, ...}:
             { address = "192.168.2.10"; prefixLength = 24; }
           ];
         };
-        firewall.allowedTCPPorts = [ 5432 6379 ];
+        firewall.allowedTCPPorts = [ 5432 31638 ];
       };
 
       services.postgresql = {
@@ -34,7 +34,7 @@ import ../make-test-python.nix ({pkgs, ...}:
         enable = true;
         bind = "0.0.0.0";
         requirePass = "turrQfaQwnanGbcsdhxy";
-        port = 6379;
+        port = 31638;
       };
     };
 
@@ -76,6 +76,7 @@ import ../make-test-python.nix ({pkgs, ...}:
 
         redis = {
           host = "192.168.2.10";
+          port = 31638;
           passwordFile = "/etc/peertube/password-redis-db";
         };
 
@@ -113,7 +114,7 @@ import ../make-test-python.nix ({pkgs, ...}:
     database.wait_for_unit("redis-peertube.service")
 
     database.wait_for_open_port(5432)
-    database.wait_for_open_port(6379)
+    database.wait_for_open_port(31638)
 
     server.wait_for_unit("peertube.service")
     server.wait_for_open_port(9000)
diff --git a/nixos/tests/web-apps/phylactery.nix b/nixos/tests/web-apps/phylactery.nix
new file mode 100644
index 000000000000..cf2689d2300d
--- /dev/null
+++ b/nixos/tests/web-apps/phylactery.nix
@@ -0,0 +1,20 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "phylactery";
+
+  nodes.machine = { ... }: {
+    services.phylactery = rec {
+      enable = true;
+      port = 8080;
+      library = "/tmp";
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit('phylactery')
+    machine.wait_for_open_port(8080)
+    machine.wait_until_succeeds('curl localhost:8080')
+  '';
+
+  meta.maintainers = with lib.maintainers; [ McSinyx ];
+})
diff --git a/nixos/tests/web-apps/writefreely.nix b/nixos/tests/web-apps/writefreely.nix
new file mode 100644
index 000000000000..ce614909706b
--- /dev/null
+++ b/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/nixos/tests/web-servers/agate.nix b/nixos/tests/web-servers/agate.nix
index e364e134cfda..e8d789a9ca44 100644
--- a/nixos/tests/web-servers/agate.nix
+++ b/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/nixos/tests/web-servers/unit-php.nix b/nixos/tests/web-servers/unit-php.nix
index 5bef7fab3eff..f0df371945e5 100644
--- a/nixos/tests/web-servers/unit-php.nix
+++ b/nixos/tests/web-servers/unit-php.nix
@@ -10,15 +10,15 @@ in {
     services.unit = {
       enable = true;
       config = pkgs.lib.strings.toJSON {
-        listeners."*:9080".application = "php_80";
-        applications.php_80 = {
-          type = "php 8.0";
+        listeners."*:9081".application = "php_81";
+        applications.php_81 = {
+          type = "php 8.1";
           processes = 1;
           user = "testuser";
           group = "testgroup";
           root = "${testdir}/www";
           index = "info.php";
-          options.file = "${pkgs.unit.usedPhp80}/lib/php.ini";
+          options.file = "${pkgs.unit.usedPhp81}/lib/php.ini";
         };
       };
     };
@@ -34,14 +34,19 @@ in {
     };
   };
   testScript = ''
+    machine.start()
+
     machine.wait_for_unit("unit.service")
+    machine.wait_for_open_port(9081)
 
     # Check so we get an evaluated PHP back
-    response = machine.succeed("curl -f -vvv -s http://127.0.0.1:9080/")
-    assert "PHP Version ${pkgs.unit.usedPhp80.version}" in response, "PHP version not detected"
+    response = machine.succeed("curl -f -vvv -s http://127.0.0.1:9081/")
+    assert "PHP Version ${pkgs.unit.usedPhp81.version}" in response, "PHP version not detected"
 
     # Check so we have database and some other extensions loaded
     for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]:
         assert ext in response, f"Missing {ext} extension"
+
+    machine.shutdown()
   '';
 })
diff --git a/nixos/tests/wine.nix b/nixos/tests/wine.nix
index 8a64c3179c51..7cbe7ac94f1e 100644
--- a/nixos/tests/wine.nix
+++ b/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/nixos/tests/wireguard/wg-quick.nix b/nixos/tests/wireguard/wg-quick.nix
index 961c2e15c30f..bc2cba911888 100644
--- a/nixos/tests/wireguard/wg-quick.nix
+++ b/nixos/tests/wireguard/wg-quick.nix
@@ -29,6 +29,8 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
 
               inherit (wg-snakeoil-keys.peer1) publicKey;
             };
+
+            dns = [ "10.23.42.2" "fc00::2" "wg0" ];
           };
         };
       };
@@ -38,6 +40,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
         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;
@@ -49,6 +52,8 @@ import ../make-test-python.nix ({ pkgs, lib, ... }:
 
               inherit (wg-snakeoil-keys.peer0) publicKey;
             };
+
+            dns = [ "10.23.42.1" "fc00::1" "wg0" ];
           };
         };
       };
diff --git a/nixos/tests/wrappers.nix b/nixos/tests/wrappers.nix
new file mode 100644
index 000000000000..08c1ad0b6b99
--- /dev/null
+++ b/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/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix
index aa55f0e3ca6f..ec48c3e11275 100644
--- a/nixos/tests/xmonad.nix
+++ b/nixos/tests/xmonad.nix
@@ -13,7 +13,9 @@ let
     import System.Environment (getArgs)
     import System.FilePath ((</>))
 
-    main = launch $ def { startupHook = startup } `additionalKeysP` myKeys
+    main = do
+      dirs <- getDirectories
+      launch (def { startupHook = startup } `additionalKeysP` myKeys) dirs
 
     startup = isSessionStart >>= \sessInit ->
       spawn "touch /tmp/${name}"
@@ -23,14 +25,15 @@ let
 
     compiledConfig = printf "xmonad-%s-%s" arch os
 
-    compileRestart resume =
-      whenX (recompile True) $
+    compileRestart resume = do
+      dirs <- asks directories
+
+      whenX (recompile dirs True) $
         when resume writeStateToFile
           *> catchIO
             ( do
-                dir <- getXMonadDataDir
                 args <- getArgs
-                executeFile (dir </> compiledConfig) False args Nothing
+                executeFile (cacheDir dirs </> compiledConfig) False args Nothing
             )
   '';
 
@@ -94,7 +97,7 @@ in {
 
     # set up the new config
     machine.succeed("mkdir -p ${user.home}/.xmonad")
-    machine.copy_from_host("${newConfig}", "${user.home}/.xmonad/xmonad.hs")
+    machine.copy_from_host("${newConfig}", "${user.home}/.config/xmonad/xmonad.hs")
 
     # recompile xmonad using the new config
     machine.send_key("alt-ctrl-q")
diff --git a/nixos/tests/xmpp/prosody.nix b/nixos/tests/xmpp/prosody.nix
index 14eab56fb821..045ae6430fd4 100644
--- a/nixos/tests/xmpp/prosody.nix
+++ b/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/nixos/tests/xmpp/xmpp-sendmessage.nix b/nixos/tests/xmpp/xmpp-sendmessage.nix
index 47a77f524c6a..8ccac0612491 100644
--- a/nixos/tests/xmpp/xmpp-sendmessage.nix
+++ b/nixos/tests/xmpp/xmpp-sendmessage.nix
@@ -6,12 +6,13 @@ let
     Please find this *really* important attachment.
 
     Yours truly,
-    John
+    Bob
   '';
 in writeScriptBin "send-message" ''
 #!${(python3.withPackages (ps: [ ps.slixmpp ])).interpreter}
 import logging
 import sys
+import signal
 from types import MethodType
 
 from slixmpp import ClientXMPP
@@ -51,11 +52,8 @@ class CthonTest(ClientXMPP):
         log.info('Message sent')
 
         # Test http upload (XEP_0363)
-        def timeout_callback(arg):
-            log.error("ERROR: Cannot upload file. XEP_0363 seems broken")
-            sys.exit(1)
         try:
-            url = await self['xep_0363'].upload_file("${dummyFile}",timeout=10, timeout_callback=timeout_callback)
+            url = await self['xep_0363'].upload_file("${dummyFile}",timeout=10)
         except:
             log.error("ERROR: Cannot run upload command. XEP_0363 seems broken")
             sys.exit(1)
@@ -67,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')
 
@@ -79,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/nixos/tests/xpadneo.nix b/nixos/tests/xpadneo.nix
new file mode 100644
index 000000000000..c7b72831fce8
--- /dev/null
+++ b/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/nixos/tests/xrdp.nix b/nixos/tests/xrdp.nix
index 0e1d521c5ace..f277d4b79525 100644
--- a/nixos/tests/xrdp.nix
+++ b/nixos/tests/xrdp.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "xrdp";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ volth ];
+    maintainers = [ ];
   };
 
   nodes = {
diff --git a/nixos/tests/yggdrasil.nix b/nixos/tests/yggdrasil.nix
index b409d9ed7853..b60a0e6b06cc 100644
--- a/nixos/tests/yggdrasil.nix
+++ b/nixos/tests/yggdrasil.nix
@@ -42,7 +42,7 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
 
         services.yggdrasil = {
           enable = true;
-          config = {
+          settings = {
             Listen = ["tcp://0.0.0.0:12345"];
             MulticastInterfaces = [ ];
           };
@@ -112,7 +112,7 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
         services.yggdrasil = {
           enable = true;
           denyDhcpcdInterfaces = [ "ygg0" ];
-          config = {
+          settings = {
             IfTAPMode = true;
             IfName = "ygg0";
             MulticastInterfaces = [ "eth1" ];
diff --git a/nixos/tests/zeronet-conservancy.nix b/nixos/tests/zeronet-conservancy.nix
new file mode 100644
index 000000000000..8cb649cbdaab
--- /dev/null
+++ b/nixos/tests/zeronet-conservancy.nix
@@ -0,0 +1,25 @@
+let
+  port = 43110;
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "zeronet-conservancy";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.zeronet = {
+      enable = true;
+      package = pkgs.zeronet-conservancy;
+      inherit port;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("zeronet.service")
+
+    machine.wait_for_open_port(${toString port})
+
+    machine.succeed("curl --fail -H 'Accept: text/html, application/xml, */*' localhost:${toString port}/Stats")
+  '';
+})
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 0b44961a3deb..29df691cecbe 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -8,7 +8,9 @@ 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
     , extraTest ? ""
     }:
diff --git a/nixos/tests/zigbee2mqtt.nix b/nixos/tests/zigbee2mqtt.nix
index 1592202fb3a7..1a40d175df83 100644
--- a/nixos/tests/zigbee2mqtt.nix
+++ b/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/nixos/tests/zrepl.nix b/nixos/tests/zrepl.nix
index 85dd834a6aaf..b16c7eddc7ae 100644
--- a/nixos/tests/zrepl.nix
+++ b/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
diff --git a/nixos/tests/zsh-history.nix b/nixos/tests/zsh-history.nix
index 355687798406..64f32a07e215 100644
--- a/nixos/tests/zsh-history.nix
+++ b/nixos/tests/zsh-history.nix
@@ -21,13 +21,13 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     default.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
 
     # Login
-    default.wait_until_tty_matches(1, "login: ")
+    default.wait_until_tty_matches("1", "login: ")
     default.send_chars("root\n")
-    default.wait_until_tty_matches(1, r"\nroot@default\b")
+    default.wait_until_tty_matches("1", r"\nroot@default\b")
 
     # Generate some history
     default.send_chars("echo foobar\n")
-    default.wait_until_tty_matches(1, "foobar")
+    default.wait_until_tty_matches("1", "foobar")
 
     # Ensure that command was recorded in history
     default.succeed("/run/current-system/sw/bin/history list | grep -q foobar")